介绍
虚拟列表主要是解决当列表数量较多时(比如上十万条数据、百万万条数据),页面内引入大量的 DOM 元素导致页面卡顿的情况,当然对于列表数量没达到足够的量级(主要根据自己的业务来衡量)但是想提高首屏加载速度时,也可以选择按需加载列表,类似于图片的懒加载,并非一次性渲染全部列表,而在滚动到页面底部的时候,再去加载剩余的数据。但是对于业务需求就是一次性返回所有数据的时候选择用虚拟列表去加载更合适。
那么接下来我开始介绍虚拟列表的实现:
思路就是用vue的for循环渲染列表,自己手动加一个滚动条,然后通过监听scroll,算出应该显示到第几个,加载对应区域的数据,列表项的dom数量始终不变。
我们这里也不废话直接代码演示
<template>
<div
:style="{ height: `${data.contentHeight}px` }"
ref="contentBox"
class="content_box"
@scroll="scroll"
>
<!--这层div是为了把高度撑开,让滚动条出现,height值为所有数据总高-->
<div
:style="{
height: `${data.itemHeight * data.listAll.length}px`,
position: 'relative',
}"
>
<!--可视区域里所有数据的渲染区域-->
<div :style="{ position: 'absolute', top: `${data.top}px` }">
<!--单条数据渲染区域-->
<div v-for="(item, index) in data.showList" :key="index" class="item">
{{ item }}
</div>
</div>
</div>
</div>
</template>
<script setup>
import {
ref,
reactive,
toRefs,
onBeforeMount,
onMounted,
watchEffect,
computed,
nextTick,
} from "vue";
const data = reactive({
listAll: [], //所有数据
showList: [],
contentHeight: 500, //可视区域高度
itemHeight: 30, //每条数据所占高度
showNum: 0, //可是区域显示的最大条数
top: 0, //偏移量
scrollTop: 0, //卷起的高度
startIndex: 0, //可视区域第一条数据的索引
endIndex: 0, //可视区域最后一条数据后面那条数据的的索引,因为后面要用slice(start,end)方法取需要的数据,但是slice规定end对应数据不包含在里面
});
const contentBox = ref();
//构造10万条数据
const getList = () => {
for (let i = 0; i < 100000; i++) {
data.listAll.push(`我是第${i}条数据呀`);
}
};
//计算可视区域数据
const getShowList = () => {
data.showNum = Math.ceil(data.contentHeight / data.itemHeight); //可视区域最多出现的数据条数,值是小数的话往上取整,因为极端情况是第一条和最后一条都只显示一部分
data.startIndex = Math.floor(data.scrollTop / data.itemHeight); //可视区域第一条数据的索引
data.endIndex = data.startIndex + data.showNum; //可视区域最后一条数据的后面那条数据的索引
data.showList = data.listAll.slice(data.startIndex, data.endIndex); //可视区域显示的数据,即最后要渲染的数据。
const offsetY = data.scrollTop - (data.scrollTop % data.itemHeight); //在这需要获得一个可以被itemHeight整除的数来作为item的偏移量,这样随机滑动时第一条数据都是完整显示的
data.top = offsetY;
};
//监听滚动事件,实时计算scrollTop
const scroll = () => {
data.scrollTop = contentBox.value.scrollTop; //element.scrollTop方法可以获取到卷起的高度
getShowList();
};
onMounted(() => {
// 等待dom渲染完毕
getList();
scroll();
});
</script>
<style scoped >
.content_box {
overflow: auto; /*只有这行代码写了,内容超出高度才会出现滚动条*/
width: 700px;
border: 1px solid red;
}
/*每条数据的样式*/
.item {
height: 30px;
padding: 5px;
color: #666;
box-sizing: border-box;
}
</style>
注:作者这里是demo 没有设置缓冲参数
有些情况行高是不确定的,最好的办法是定义一个预测高度,等元素渲染出来马上把该元素高度缓存,等到下次该元素出来的话就用这个高度初始化,然后该元素到了可视区再次缓存高度(解决表格宽度自适应)