使用场景
在日常开发中,当服务端返回一个列表时,如果列表长度比较短的话可以直接将列表渲染到页面当中,但是对于长列表
来说,将其渲染到页面中将会花费很长时间,以下面这段代码为例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <template> <ul class="list"> <li class="list-item" v-for="item in data.list" :key="item">{{ item }}</li> </ul> </template>
<script setup lang="ts"> import { reactive, onMounted } from "vue";
const data = reactive({ list: [], });
onMounted(() => { const tmp = []; const startTime = Date.now();
for (let i = 0; i < 10000; i++) { tmp.push(i); }
data.list = tmp;
console.log("js执行时间", (Date.now() - startTime) / 1000);
setTimeout(() => { console.log("js执行时间 + 渲染时间", (Date.now() - startTime) / 1000); }, 0); }); </script>
|
在以上代码中,我们将 10000 条数据一次性渲染到了页面当中,总用时为1.588s,其中渲染时间
占了1.587s。可以得出在渲染列表时,大部分时间都花在了页面渲染
阶段。究其原因是因为渲染的dom节点
太多,因此可以通过减少渲染dom节点
数量来降低页面渲染
阶段的用时。虚拟列表
正是解决这个问题的有效方法。
什么是虚拟列表
虚拟列表
是指仅仅渲染可视区域
内的列表项,超出的不渲染,这样的话每次渲染的节点数将极大的减少,相应的渲染时间
也会大幅降低。
虚拟列表
的渲染主要分为两部分。首先是首次渲染
,直接取列表的前n
条数据即可。其次是滚动渲染
,监听列表容器
的滚动事件,并实时计算当前需要渲染的子列表[startIndex, endIndex]
。
实现
首先实现虚拟列表
的Dom结构
。结构如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| <template> <div class="virtual-list" ref="virtualListContainer"> <div class="virtual-list-placeholder" :style="{ height: `${itemHeight * list.length}` }" ></div> <div class="virtual-list-box" :style="{ transform: `translate3d(0,${offsetY}px, 0)` }" > <div class="virtual-list-box-item"></div> </div> </div> </template>
<style lang="less"> * { box-sizing: border-box; }
.virtual-list { overflow: auto; position: relative; max-height: 400px; border: 1px solid #ccc;
&-box { position: absolute; top: 0; left: 0; width: 100%; pointer-events: none;
&-item { height: 50px; text-align: center; line-height: 50px; } } } </style>
|
注意
virtual-list-box列表盒子
是相对virtual-list容器盒子
进行定位的,当容器盒子
滚动时列表盒子
也会跟着滚动,因此需要给列表盒子
设置transform: translate3d(0,offsetY, 0)
属性将其移动到视口区域。
计算前进行以下约定
- 容器的可视高度为
visibleHeight
- 列表项的高度为
itemHeight
- 容器可视区域内列表项的个数为
virtualListLength
- 容器滚动的距离为
scrollTop
需要计算的值包括:
- 容器滚动的距离:
scrollTop = virtualListContainer.scrollTop
- 计算列表开始位置:
startIndex = Math.floor(scrollTop / itemHeight)
- 计算列表结束位置:
endIndex = startIndex + virtualListLength
- **计算列表盒子的偏移量(translateY 的值)**:
offsetY = Math.floor(scrollTop / itemHeight) * itemHeight
点我查看完整代码
参考