1. 技术背景:虚拟滚动为何成为大数据量列表的解决方案
当列表数据量达到数千甚至上万条时,传统的渲染方式会尝试将全部 DOM 节点一次性插入页面,结果必然导致严重的性能瓶颈——页面卡顿明显、滚动响应迟钝,用户体验急剧下滑。那么,是否存在一种方案既能完整呈现数据集,又不会拖累浏览器性能?答案是肯定的。虚拟滚动(Virtual Scrolling)正是为解决这一痛点而生的前端优化技术。
其核心设计思路非常清晰:仅渲染当前可视区域内的那一部分数据,而不可见的内容在 DOM 中完全不出现。这样一来,无论列表后方有多少条数据,屏幕上始终只维持有限的节点数量,渲染效率与滚动流畅度均得到显著提升。

2. 固定高度方案(v1):基础实现与核心逻辑
2.1 核心实现原理
固定高度版本的实现遵循一个直观的思路:
- 采用一个固定高度的容器作为“视口”,相当于一扇窗户,用户透过它看到的即是列表当前展示的内容。
- 同时放置一个与全部数据总高度相等的占位元素,用于撑开滚动条——没有这个滚动“尾巴”,浏览器无法感知还有大量数据未被查看。
- 接下来需要动态计算:随着滚动条的滑动,实时算出当前视口中应该呈现哪些数据,然后仅渲染这些元素。
- 最后借助 translate3d 精确控制列表项的位置,这种方式性能出色,且不会触发回流与重排。
2.2 关键代码解析
模板结构
class="viewport" ref="viewport" @scroll="handleScroll">
class="scroll-bg" ref="scrollBg">
class="scroll-list"
ref="scrollList"
:style="{ transform: `translate3d(0, ${offset}px,0)` }"
>
v-for="item in visibleItems" :key="item.id">
item="item">
Props 定义
const props = defineProps<{
items: any[]; // 列表数据
remain: number; // 显示个数
size: number; // 每个元素的高度
}>();
核心状态管理
此处通过 start 和 end 标记当前屏幕应显示数据的起始与结束索引。但有一个关键细节:滚动时列表项可能只移动了一半,如果仅渲染完整的 start 到 end 区间,就可能出现空白区域。因此需要在前后各多加载一些数据,相当于准备约三个屏幕的数据量。
此外,利用 offset 控制列表外层容器的偏移量——用户每完整滑过一个项,偏移量就累加一个项的高度。最终对传入的总数据进行截取,只渲染屏幕所需的那部分,从而达到性能优化的目标。
// 数组的起始值
const start = ref(0);
const end = ref(props.remain);
// 数组渲染dom的偏移量
const offset = ref(0);// 前面预先加载的个数
const prevCount = computed(() => {
return Math.min(start.value, props.remain);
});
// 后面预先加载的个数
const nextCount = computed(() => {
return Math.min(props.items.length - end.value, props.remain);
});
// 计算当前需要显示的数据
const visibleItems = computed(() => {
const startIndex = start.value - prevCount.value;
const endIndex = end.value + nextCount.value;
return props.items.slice(startIndex, endIndex);
});
滚动事件处理
监听滚动事件:获取当前滚动距离,计算在此过程中完整滑过的列表项数量,然后据此重新计算 start 与 end,并同步更新偏移量。
const handleScroll = () => {
// 滚动的距离
const scrollTop = viewportRef.value?.scrollTop ?? 0;
// 滚动过去的完整个数
const scrollCount = Math.floor(scrollTop / props.size);
start.value = scrollCount;
end.value = start.value + props.remain;
offset.value = start.value * props.size - prevCount.value * props.size;
};
2.3 性能优化策略
- 仅渲染当前可视区域的元素,大幅削减 DOM 节点数量
- 借助 CSS 硬件加速(translate3d),避免每次滚动都触发重排
- 在首尾设置预加载缓冲区,防止快速滚动时出现白屏
- 利用 Vue 的响应式系统精确更新数据切片,提升计算效率
2.4 使用示例
:size="40" :items="items" :remain="10">
"{ item }">
:title="item.title">
</template>
</template>
3. 动态高度方案(v2):应对复杂业务场景的进阶实现
固定高度方案虽然高效实用,但在真实业务中,列表项的高度往往参差不齐。例如,某列表的第一条可能是简短文案,第二条却包含图片加多行说明,高度差异明显。此时必须支持动态高度,才能满足更复杂的业务需求。
3.1 核心实现原理
动态高度方案的核心在于两项关键技术:位置缓存与二分查找。
3.1.1 位置缓存
- 为每一项维护一条记录,存储其 top、bottom、height 三个数值
- 初始时所有项均以默认高度预估,待实际渲染完成后,再用真实高度替换
- 一旦某项目高度发生变化,其后所有项的位置都需要重新校正
3.1.2 二分查找
- 如何快速定位滚动位置对应的起始索引?若逐个向前推算,数据量庞大时性能会急剧下降
- 因此采用二分查找,将定位时间复杂度从 O(n) 降至 O(log n),大幅提升检索效率
3.2 算法详解
3.2.1 二分查找算法
此二分查找实现稍有特殊:用户滚动的位置可能落在某个项的正中间,因此当找到对应位置时,并不一定恰好位于某个项的开头。为此,使用一个 temp 变量记录当前最接近滚动位置的索引。
const binarySearch = (scrollTop) => {
let start = 0;
let end = positions.length - 1;
let temp = null;
while (start <= end) {
let mid = (start + end) | 0;
let midBottom = positions[mid].bottom;
if (scrollTop === midBottom) {
return mid + 1;
} else if (scrollTop < midBottom) {
if (temp === null || temp > mid) {
temp = mid;
}
end = mid - 1;
} else {
start = mid + 1;
}
}
return temp || 0;
};
3.2.2 位置更新算法
当某个列表项的实际高度被测量出来后,需与缓存中的旧高度进行比对。若存在差异,则更新当前位置,同时该位置之后的所有项都需要重新计算 top 和 bottom。
const { height } = el.getBoundingClientRect();
const id = Number(el.getAttribute("vid")) || 0;
const oldHeight = positions.find((p) => p.id === id)?.height ?? 0;
const diffHeight = height - oldHeight;
if (diffHeight !== 0) {
// 高度有变化
const index = positions.findIndex((p) => p.id === id);
positions[index]!.height = oldHeight + diffHeight;
positions[index]!.bottom = positions[index]!.top + height;
//后面的都需要更新
for (let i = index + 1; i < positions.length; i++) {
positions[i]!.top = positions[i - 1]!.bottom;
positions[i]!.bottom = positions[i]!.top + positions[i]!.height;
}
}
3.3 性能优化
3.3.1 节流处理
滚动事件触发极为频繁,因此借助 lodash.throttle 进行节流控制,默认间隔设为 100ms。这样既不影响用户的滚动体验,又能避免高频触发引发的大规模计算开销。
3.3.2 预加载机制
与前一个版本类似,通过前后预加载项数(prevCount / nextCount)防止快速滚动时出现白屏。实际效果显著,即使数据量达到上万条,用户依然能够顺畅滚动,体验如同浏览普通列表一般自然。
团队介绍
「智慧家技术平台-智家APP开发」通过持续迭代演进移动端一站式接入平台,为三翼鸟APP、智家APP等多个应用提供基础运行框架、系统通用能力API、日志、网络访问、页面路由、动态化框架、UI组件库等移动端开发通用基础设施;同时依托Z·ONE平台为三翼鸟子领域提供项目管理和技术实践支撑能力,完成从代码托管、CI/CD系统、业务发布到线上实时监控等DevOps与工程效能基础设施的搭建。
