虚拟滚动技术能有效解决长列表渲染难题,通过智能渲染可视区域大幅提升性能。本文将深入解析其实现原理与优化策略。
面对海量数据列表的传统渲染方式会引发严重的性能问题,虚拟滚动技术应运而生。这项创新方案仅渲染用户可视范围内的元素,通过精准控制DOM节点数量来确保流畅的交互体验。

该组件基于以下核心思想实现:
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">
const props = defineProps<{
items: any[]; // 列表数据
remain: number; // 显示个数
size: number; // 每个元素的高度
}>();
用start和end来记录当前显示的屏幕需要显示的数组的起始位置,但是在滚动过程中会存在一个列表元素滚动了一部分的情况,会有空白的显示问题,所以在增加前后预加载的数据,相当于加载3个屏幕的数据;
用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);
});
获取滚动的距离,重新计算屏幕中显示数据的开始位置和结束位置,更新偏移量
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;
};
:size="40" :items="items" :remain="10">
"{ item }">
:title="item.title">
</template>
</template>
当列表的内容是不确定的,可变的时候,固定高度不再满足业务需求,需要增加可变的选项来满足可变高度的列表
用于快速定位当前滚动位置对应的起始项索引:
区别于平常的二分算法,由于可能滚动的位置在一个列表元素的中间位置,所以增加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;
};
当元素高度发生变化时,更新位置缓存:
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;
}
}
滚动事件使用lodash.throttle进行节流处理,默认100ms间隔。
根据前后预加载数量(prevCount/nextCount)渲染额外项,减少快速滚动时的白屏现象。
虚拟滚动技术通过创新的渲染机制和优化算法,为海量数据列表提供了高效的解决方案,让开发者能够轻松实现流畅的用户体验。