在前端的场景中,有很多数据无限的下拉列表,比如商品列表和或者聊天记录列表等等。
随着数据的不断加载,页面中可能会渲染出几千几万个相同的元素,这样对性能的损耗是非常大的。
为了解决这一问题,就出现了切片渲染和虚拟列表的概念,这里就来说一下虚拟列表。
虚拟列表是使用技术手段实现长列表渲染的一种方法,操作的原理就好比,就好比电梯和楼层的关系,通过索引来显示需要显示数据,只不过虚拟列表中的电梯是一段,可能同时可以到达多个连续的楼层,比如电梯的长度是3,那就可以同时到达123,234,345,等等。
用代码实现思路如下:
1、给定一个列表元素,使用for循环加载子元素。
2、确定视口可以展示几个元素。
3、截取数组,按照滚动到顶部的位置,计算开始显示数据的索引和结束的索引,结束的索引=开始的索引+视口可以展示的元素个数。
4、滚动条改变时,改变开始的索引和结束的索引。
5、计算填充,让滚动更加丝滑。
以下是代码实现:
<template>
<div
ref="listContainer"
class="list-container"
@scroll.passive="scrollHandle"
:style="{
'--rowHeight': oneHeight + 'px',
}"
>
<!-- 撑开进度条的方法,滚动效果不好 -->
<!-- <div
class="phantom"
:style="{
height: list.length * oneHeight + 'px',
}"
></div> -->
<!-- 第二种使用,使用padding撑开进度条,滚动效果好 -->
<div class="list" ref="list" :style="listBlankPadding">
<div class="item" v-for="item in showList" :key="item.index">
第{{ item.value }}项列表
</div>
</div>
</div>
</template>
<script>
export default {
props: {
list: {
type: Array,
default() {
return [];
},
},
// 单条数据的高度
oneHeight: {
type: Number,
default: 50,
},
},
data() {
return {
start: 0,
containterHeight: 0,
// 单页面也是的数量
showNumber: 0,
};
},
computed: {
end() {
// 不能最直接取start+showNumber,要判断数据有没有
return this.list[this.start + this.showNumber]
? this.start + this.showNumber
: this.list.length - 1;
},
listBlankPadding() {
return {
paddingTop: this.start * this.oneHeight + "px",
paddingBottom: (this.list.length - this.end) * this.oneHeight + "px",
};
},
// 可视数组
showList() {
return this.list.slice(this.start, this.end);
},
},
mounted() {
this.getListHeight();
// 改变窗口获取可显示的条数
window.onresize = this.getListHeight;
},
methods: {
scrollHandle() {
const marginTop = this.$refs["listContainer"].scrollTop;
// ~~等于取整操作
this.start = ~~(marginTop / this.oneHeight);
// 第一种改变填充进度条时,需要改变改变顶部的位置
// this.$refs["list"].style.transform = `translateY(${marginTop}px)`;
},
getListHeight() {
this.$nextTick(() => {
// 头部和底部会有显示两个半条的情况,所以+2
this.showNumber =
~~(this.$refs["listContainer"].offsetHeight / this.oneHeight) + 2;
});
},
},
};
</script>
<style scoped>
.list-container {
width: 100%;
height: 100%;
overflow-y: auto;
position: relative;
background: turquoise;
}
.phantom {
width: 10px;
z-index: -1;
position: absolute;
}
.list {
position: absolute;
inset: 0;
}
.item {
height: var(--rowHeight);
}
</style>
最终实现的效果如下所示:
vue虚拟列表
当我们在父组件当中使用时,只需将组件导入,传入list就可以,单行高度不传时默认50px。
<template>
<div id="app">
<VirtureList :list="list" />
</div>
</template>
<script>
import VirtureList from "@/components/virture-list.vue";
export default {
name: "App",
components: {
VirtureList,
},
computed: {
list() {
return Array(100)
.fill("")
.map((item, index) => {
return {
value: index,
index,
};
});
},
},
};
</script>
<style>
#app {
height: 100vh;
width: 100vw;
}
</style>