虚拟滚动的实现(适合大量的列表数据)

假设我们拥有10w条数据,需要在Web上进行展现,但是如果我们进行实际的渲染就会发现,整个初次渲染的成本的非常之高,用户的体验是非常差的。所以诞生了一种虚拟滚动条的概念,本质上就是进行按需渲染,我们永远只渲染当前所展现的数据,一个滚动区域的视口是固定的,比如400px,我们可以计算出整个滚动区域的具体需要展现那一部分的数据,进行按需渲染。

以下是我实现了一个虚拟滚动的DEMO后的DOM结构,我事实上渲染了10w条数据,但在同一时间内,永远只渲染20条的数据(因为视口=400px,单个item高度等于20px),如此一来,我们就能减少初次渲染时的负担。

Code

<template>
<div id="app">
<div class="scroll-box" @scroll="onScrollHandle">
<ul class="wrap" :style="`height:${wrapHeight}px`">
<li
class="item"
v-for="v of renderList"
:key="v.id"
:style="`color:${v.color};top:${20 * v.id}px`"
>{{v.id}}</li>
</ul>
</div>
</div>
</template>
<script>
const viewportHeight = 400;
const viewportItemHeight = 20;
const viewportItemCount = 100000;
// 为了避免肉眼可见的消失 + 1
const renderCount = viewportHeight / viewportItemHeight + 1;

export default {
data() {
const list = Array(viewportItemCount)
.fill(1)
.map((v, i) => {
const color = "#" + Math.floor(Math.random() * 256 ** 3).toString(16);
const id = i;
return {
color,
id
};
});
return {
list,
renderList: [],
wrapHeight: 0
};
},
created() {
this.wrapHeight = viewportItemHeight * viewportItemCount;
this.renderList = this.calcluteRenderList(0);
},
methods: {
calcluteRenderList(scrollTop) {
const renderTopCount = Math.floor(scrollTop / viewportItemHeight);
return this.list.slice(renderTopCount, renderTopCount + renderCount);
},
onScrollHandle(e) {
this.renderList = this.calcluteRenderList(e.target.scrollTop);
}
}
};
</script>
<style lang="stylus">
html, body
padding 0
margin 0
.scroll-box
overflow-y scroll
height 400px
.wrap
overflow hidden
padding 0
margin 0
position relative
.item
height 20px
position absolute
list-style none
</style>

事实上这个代码是非常简单的,首先我们建立了两个列表,一个是完整的数据,一个是真实用来渲染的列表,并在滚动事件触发的时候,不断去计算当前到底需要哪些列表项来展示。这里需要注意的有两点

  1. 单个Item的高度必须固定,否则无法进行计算
  2. wrap区域需要进行一次初始化,值为item的高度和

总结

当然这种长列表的需求一般来说可以用分页去解决,不要为了用而用,因为长列表滚动查找起来对于用户并不友好。在某些特殊情况下还是可以的。