《如何以长列表的形式显示上万条数据》

分析问题,确定方案

由于最终的效果是一个长列表的形式,那么常规的分页渲染是不可取的,这个时候我们可以用虚拟列表来实现需求。

什么是虚拟列表

虚拟列表就是只对可见区域进行渲染,对非可见区域的数据不渲染或者只部分渲染,从而来减少消耗,提高用户体验,他是一种长列表的优化方案。

实现思路

  1. 写一个可见区域的 div,固定其高度,通过 overflow 使其允许向 Y 轴滚动;
  2. 计算区域可以显示的数据的条数,这个可以用可见区域的高度除以单挑数据的高度;
  3. 监听滚动,当滚动条发生变化时,计算出卷起数据的高度;
  4. 计算区域内数据的起始索引,也就是区域内的第一条数据,这个用卷起的高度初一单条数据的高度得到;
  5. 计算区域内数据的结束的索引,通过起始索引+可显示的数据条数可以拿到;
  6. 取起始索引和结束索引中间的数据,渲染到可见区域;
  7. 计算起始索引对应的数据在整个列表中偏移位置并设置到列表上;

整个步骤下来,最终效果就是,不论我们怎么滚动,我们改变的只是滚动条的高度和可视区的元素内容,每次只会渲染固定的条数,不会增加多余的元素。

比如说在 vue 项目中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<template>
<div
:style="{ height: `${contentHeight}px` }"
class="content_box"
@scroll="scroll"
>
<!--这层div是为了把高度撑开,让滚动条出现,height值为所有数据总高-->
<div
:style="{
height: `${itemHeight * listAll.length}px`,
position: 'relative',
}"
>
<!--可视区域里所有数据的渲染区域-->
<div :style="{ position: 'absolute', top: `${top}px` }">
<!--单条数据渲染区域-->
<div v-for="(item, index) in showList" :key="index" class="item">
{{ item }}
</div>
</div>
</div>
</div>
</template>

<script>
export default {
name: "list",
data() {
return {
listAll: [], //所有数据
showList: [], //可视区域显示的数据
contentHeight: 500, //可视区域高度
itemHeight: 30, //每条数据所占高度
showNum: 0, //可是区域显示的最大条数
top: 0, //偏移量
scrollTop: 0, //卷起的高度
startIndex: 0, //可视区域第一条数据的索引
endIndex: 0, //可视区域最后一条数据后面那条数据的的索引,因为后面要用slice(start,end)方法取需要的数据,但是slice规定end对应数据不包含在里面
};
},
methods: {
//构造10万条数据
getList() {
for (let i = 0; i < 100000; i++) {
this.listAll.push(`我是第${i}条数据呀`);
}
},
//计算可视区域数据
getShowList() {
this.showNum = Math.ceil(this.contentHeight / this.itemHeight); //可视区域最多出现的数据条数,值是小数的话往上取整,因为极端情况是第一条和最后一条都只显示一部分
this.startIndex = Math.floor(this.scrollTop / this.itemHeight); //可视区域第一条数据的索引
this.endIndex = this.startIndex + this.showNum; //可视区域最后一条数据的后面那条数据的索引
this.showList = this.listAll.slice(this.startIndex, this.endIndex); //可视区域显示的数据,即最后要渲染的数据。实际的数据索引是从this.startIndex到this.endIndex-1
const offsetY = this.scrollTop - (this.scrollTop % this.itemHeight); //在这需要获得一个可以被itemHeight整除的数来作为item的偏移量,这样随机滑动时第一条数据都是完整显示的
this.top = offsetY;
},
//监听滚动事件,实时计算scrollTop
scroll() {
this.scrollTop = document.querySelector(".content_box").scrollTop; //element.scrollTop方法可以获取到卷起的高度
this.getShowList();
},
},
mounted() {
this.getList();
this.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>