当列表数据过长,会遇到不使用分页方式来加载长列表的需求。如在数据长度大于 1000 条情况,DOM 元素的创建和渲染需要的时间成本很高,完整渲染列表所需要的时间不可接受,同时会存在滚动时卡顿问题。
解决该卡顿问题的重点在于如何降低长列表DOM渲染成本问题。
1、可以通过后端分页,当我们的列表滑到最底下时,再请求另一页的数据
2、虚拟列表,降低长列表DOM渲染成本问题
虚拟列表的实现
实现虚拟列表就是处理滚动条滚动后的可见区域的变更,其中具体思路如下:
1.计算当前可见区域起始数据的 startIndex
2.计算当前可见区域结束数据的 endIndex
3.计算当前可见区域的数据,并渲染到页面中
4.计算 startIndex 对应的数据在整个列表中的偏移位置 startOffset,并设置到列表上
基础实现
我们首先要考虑的是虚拟列表的 HTML、CSS 如何实现:
1、列表元素(.list-view)使用相对定位
2、使用一个不可见元素(.list-view-phantom)撑起这个列表,让列表的滚动条出现
3、列表的可见元素(.list-view-content)使用绝对定位,left、right、top 设置为 0
html
<template>
<div
class="list-view"
:style="{
height: `${height}px`,
}"
@scroll="handleScroll"
ref = 'container'
>
<div
class="list-view-phantom"
:style="{
height: contentHeight,
}"
></div>
<ul ref="content" class="list-view-content">
<li
class="list-view-item"
:style="{
height: itemHeight + 'px',
}"
v-for="(item, index) in vData"
:key="index"
>
{{ item.img }}
</li>
</ul>
</div>
</template>
js
<script lang='ts'>
import { Component, Prop, Vue } from 'vue-property-decorator'
@Component({
name: 'VList'
})
export default class LkModel extends Vue {
$refs!: {
container: any,
content: any
};
@Prop({ type: Array, required: true })
data!: [];
height = 400
itemHeight = 30
vData = []
get contentHeight () {
return this.data.length * this.itemHeight + 'px'
}
updateVdata (scrollTop:number):void {
scrollTop = scrollTop || 0
// 获取可见区域的可见列表数量
const vCount = Math.ceil(this.$refs.container.clientHeight / this.itemHeight)
// 获取可见区域的开始数据索引
const start = Math.floor(scrollTop / this.itemHeight)
// 获取可见区域的结束数据索引
const end = start + vCount
console.error(start, end)
// 计算出可见区域的数据,让vue更新
this.vData = this.data.slice(start, end)
// 刚列表内容显示在可视区域
this.$refs.content.style.transform = `translate(0, ${start * this.itemHeight}px)`
}
handleScroll ():void {
const scrollTop = this.$refs.container.scrollTop
this.updateVdata(scrollTop)
}
mounted ():void {
this.updateVdata()
}
}
</script>
css
<style lang='less' scoped>
.list-view {
overflow: auto;
position: relative;
width: 200px;
border: 1px solid #aaa;
}
.list-view-phantom {
position:absolute;
top: 0;
left: 0;
right: 0;
z-index: -1;
}
.list-view-content {
position:absolute;
top: 0;
left: 0;
right: 0;
}
.list-view-item {
padding: 5px;
color:#666;
line-height: 30px;
box-sizing: border-box;
}
</style>