对于有成千上万条数据列表的渲染,前端页面的初始化加载和滚动操作都会带来一定程度上的卡顿。虚拟列表的原理是只渲染可视区域内的列表元素,在可视区域外的dom元素都不会被渲染。当然这个渲染范围,通过调整也可以大于可视区域。
实现原理
- 根据滚动区域的高度和列表项的高度,计算出每次实际需要渲染的列表项的个数。
- 设置数组的
截取开始点和截取结束点,在包含所有数据的数组中截取出实际渲染的数组。 - 监听滚动事件,获取
scrollTop值,并根据该值重新计算截取开始点和截取结束点。 - 设置虚拟滚动条。在渲染的滚动列表下方,放入一个空
div。只需动态设置其高度,使其高度加上已渲染的所有列表项的高度,大于等于所有列表项的高度之和。目的是撑起滚动内容,并获得较为真实的滚动条。实际的计算中会出现误差,可按实际情况进行调整。 - 滚动时,已渲染的列表部分会被滚上去,此时,我们借用
transform来对其进行向下平移。向下平移的距离为scrollTop值。
实现代码
本文是在vue中实现了虚拟滚动。下面是html部分:
<div id="virtual-container" @scroll="scrollService.onScroll">
<div
:style="{ transform: `translateY(${scrollService.tranlateY.value}px)` }"
>
<div
class="virtual-item"
v-for="item in scrollService.visibleList.value"
:key="item.id"
>
{{ item.name }}
</div>
</div>
<div
class="virtual-height"
:style="{ height: scrollService.virtualHeight.value + 'px' }"
></div>
</div>
对应的js部分:
import { ref, computed } from "vue";
export class VirtualScroll {
// 列表的所有数据
private scrollList = ref<any[]>([]);
// 滚动区域高度和列表项高度
private containerHeight = 555;
private listItemHeight = 23;
// 整个列表向上平移的高度
tranlateY = ref(0);
// 截取数据的起点和终点
startIndex = ref(0);
endIndex = ref(Math.ceil(this.containerHeight / this.listItemHeight));
// 虚拟高度,用来撑起内容,获取滚动条
virtualHeight = computed(() => {
return (
(this.scrollList.value.length - this.visibleList.value.length) *
this.listItemHeight +
this.listItemHeight
);
});
// 实际展示的列表数据
visibleList = computed(() => {
return this.scrollList.value.slice(
this.startIndex.value,
this.endIndex.value
);
});
constructor(
list: any[],
containerHeight: number = 555,
listItemHeight: number = 23
) {
this.scrollList.value = list;
this.containerHeight = containerHeight;
this.listItemHeight = listItemHeight;
}
// 滚动函数
onScroll(e: any) {
const scrollTop = e.target?.scrollTop;
this.tranlateY.value = scrollTop;
this.startIndex.value = Math.floor(scrollTop / this.listItemHeight);
this.endIndex.value =
this.startIndex.value +
Math.ceil(this.containerHeight / this.listItemHeight);
}
}
vue组件中引入虚拟滚动服务:
import { VirtualScroll } from "./virtualScroll";
const scrollService = new VirtualScroll(scrollList.value);