JavaScript原生实现简单虚拟列表

javascript简单实现一个虚拟列表,理解了原理其实挺简单的。

背景

在公司项目中,需要给商品配置大量的属性值,可能其中一个属性的值数量就有成百上千条。

一个商品会有很多属性,如果把这些属性和属性值同时都渲染出来,就会导致在一个页面上渲染很多的节点,导致浏览页面时卡顿

从而导致应用性能用户体验都不好。

为了解决上述原因导致的应用性能和用户体验问题

可能比较常见的解决方法就是:分页

另一个就是近两年常听到的一个解决方法:虚拟列表

下面简单分析下虚拟列表的实现方式

分析

简单分析下,虚拟列表主要由以下几个部分构成:

  • 实际列表: 指你所需要展示的全部数据渲染成的列表
  • 可视区: 指数据需要展示时能看到的最大区域
  • 已加载区
  • 未加载区

如下图所示:

虚拟列表示意图

由图可以看出

我们实际所能看到的数据(即可视区展示的内容)只占所有的数据的很小一部分

还有很大一部分是我们暂时不需要渲染出来的

如果我们一次性把其他不需要的数据也同时渲染到页面上,会造成DOM的浪费。

所以我们考虑:能不能只渲染可视区的DOM节点,其他的节点等需要用到的时候再渲染出来,使页面上始终只存在可视区的节点,这样就大大减少了DOM节点的数量。

实现

页面代码如下

  • #listView: 表示你需要展示列表内容的区域,即可视区,本文中为 body 高度
  • #zhanwei: 用来表示占位,是滚动条出现,它的高度就是所有数据渲染到页面时的高度,需要在js中计算
  • #listContent: 是实际列表内容的容器。
1
2
3
4
5
6
<div id="listView" class="list-view">
<div id="zhanwei" class="zhanwei"></div>
<ul id="listContent" class="list-content">

</ul>
</div>
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

body{
height: 400px;
}

.list-view {
height: 400px;
position: relative;
overflow-y: auto;
}

.zhanwei {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
}

.list-content li {
height: 100px;
}

.list-content li:nth-of-type(odd) {
background: #00ccff;
}

.list-content li:nth-of-type(even) {
background: #ffcc00;
}
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
// 模拟常列表的数据
function setLongData() {
let data = [];
for (let i = 0; i < 1000000; i++) {
data.push({id: "item" + i, value: Math.random() * i})
}
return data;
}

function selectDom(selector) {
return document.querySelector(selector)
}

// 加载数据并插入DOM到页面
function loadData(start, end) {
// 截取数据
let sliceData = setLongData().slice(start, end)
// 创建虚拟DOM
let F = document.createDocumentFragment();
for (let i = 0; i < sliceData.length; i++) {
let li = document.createElement("li");
li.innerText = JSON.stringify(sliceData[i])
li.className = sliceData[i].id
F.appendChild(li)
}
selectDom(".list-content").innerHTML = "";
selectDom(".list-content").appendChild(F)
}

// 设置占位DOM的高度
document.getElementById("zhanwei").style.height = `${100 * setLongData().length}px`

// 计算可视区能展示几列数据,此处假设一列的高度为 100 px
let count = Math.ceil(document.body.clientHeight / 100)
let startIndex = 0; // 可视区第一列的索引
let endIndex = count; // 可视区最后一列的索引
loadData(startIndex, endIndex)

// 滚动加载数据方法
function scrollFunction() {
// 获取滚动条距离可视区顶部的距离
let scrollTop = document.getElementById("listView").scrollTop;
startIndex = Math.floor(scrollTop / 100);
endIndex = startIndex + count;
loadData(startIndex, endIndex)
// 滚动时内容区会发生偏移
// 通过 transform:translate3d将偏移的内容移回可视区
document.getElementById("listContent").style.transform = `translate3d(0, ${startIndex * 100}px, 0)`
}

document.getElementById("listView").addEventListener("scroll", scrollFunction)

最终结果如图,可以看出DOM节点的数量始终没有变化

虚拟列表结果

最后

这只是一个简单实现,要在实际项目中应用还需要深入研究。

感谢您的阅读