虚拟列表
虚拟列表是指只在用户可视区域内的列表数据进行渲染,在不可见区域的数据不进行渲染或者进行部分渲染。
为什么要使用虚拟列表
工作中经常会遇到滚动分页的业务场景,不同于普通分页。普通分页在分页查询后只会渲染展示当前页的数据。而滚动分页则会在每次查询之后,将数据插入到之前数据的尾部,将全部的数据都进行渲染展示。当用户滚动多次之后,数据量很大,将这些数据全部渲染出来会占用很多内存,于是就会出现滚动卡顿的情况。而使用虚拟列表时,只会渲染可视区域内的列表数据,所以会提高渲染性能,优化用户体验。
虚拟列表的原理
- 获取可视区域高度,列表项高度,计算可展示的数量。
- 通过展示数量获取展示项结束索引以及实际展示列表。
- 当滚动时动态计算开始索引,结束索引以及实际展示列表,对可视区域重新进行渲染。
注意点:
- 由于只对可视区域内的列表进行渲染,所以不会出现滚动条,就需要一个与实际列表总高度一致的div进行占位,还原滚动条。
- 滚动时会将实际展示列表容器进行上移,所以需要偏移量让它向下移动到可视区域内。
- 不需要对实际展示列表容器实时进行偏移,仅当要更新列表时再次进行偏移,否则会没有数据滚动效果。
虚拟列表的具体实现
<template>
<div class="page" ref="page" @scroll="onScroll">
<!-- 占位 -->
<div class="placeholder" :style="{ height: listHeight + 'px' }"></div>
<!-- 列表展示容器 -->
<div :style="{ transform: getTransform }">
<!-- 实际展示的列表 -->
<div class="item" :key="item.id" v-for="item in showList">
{{ item.id }}
</div>
</div>
</div>
</template>
<script>
/*eslint-disable*/
let id = 0;
const add = () => {
let arr = [];
let len = id + 10;
let i = id;
while (i < len) {
arr.push({ id: i });
i++;
}
id = i;
return arr;
};
export default {
name: "virtualList",
data: () => {
return {
totalList: [], // 列表实际数据
screenHeight: 0, // 容器高度
itemHeight: 100, // 每个列表项的高度
start: 0, // 展示列表开始索引
end: 0, // 展示列表结束索引
count: 0, // 容器内可展示的列表数量
startOffset: 0, // 际列表展示容器的偏移量
};
},
computed: {
// 计算实际列表占用的总高度
listHeight() {
return this.totalList.length * this.itemHeight;
},
// 计算展示的列表数据
showList() {
return this.totalList.slice(this.start, this.end);
},
// 计算展示列表容器的偏移量
getTransform() {
return `translate3d(0,${this.startOffset}px,0)`;
},
},
mounted() {
// mock数据
this.totalList = [...this.totalList, ...add()];
// 获取可视区域的高度
this.screenHeight = this.$refs.page.offsetHeight;
// 计算可视区域内可展示的列表数量
this.count = Math.floor(this.screenHeight / this.itemHeight);
// 计算展示列表结束索引
this.end = this.start + this.count;
},
methods: {
onScroll() {
// 当前滚动条滚动的高度
const scrollTop = this.$refs.page.scrollTop;
// 计算实际展示列表项的开始索引
this.start = Math.floor(scrollTop / this.itemHeight);
// 计算实际展示列表项的结束索引
this.end = this.start + this.count;
// 计算偏移量
this.startOffset = scrollTop - (scrollTop % this.itemHeight);
// 模拟到底部时,获取新数据场景
if (
this.$refs.page.scrollTop + this.$refs.page.clientHeight ===
this.$refs.page.scrollHeight
) {
this.totalList = [...this.totalList, ...add()];
}
},
},
};
</script>
<style lang="less" scoped>
.page {
width: 500px;
height: 500px;
overflow-y: auto;
position: relative;
.placeholder {
position: absolute;
left: 0;
top: 0;
right: 0;
z-index: -1;
}
.item {
height: 100px;
line-height: 100px;
}
}
</style>