列表展示是前端一种常见的渲染方式,但是当数据量过多的时候怎么办呢?是把所有数据都渲染出来吗?这样页面上的元素过多会造成页面卡顿,影响用户体验,有没有更好的方式呢?这里我们想到了虚拟列表,即展示给用户的是一个虚拟的长列表,其实我们渲染的只是截取的其中一部分的数据,当滚动的时候及时更新这些数据,这样既可以展示所有数据又可以减少DOM元素
基础版虚拟列表的实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>基础列表</title>
</head>
<style>
#container {
position: relative;
width: 500px;
height: 500px;
border: 1px solid #000;
overflow-y: scroll;
}
#placeholder {
width: 1px;
position: absolute;
top: 0;
left: 0;
}
#list {
width: 100%;
position: absolute;
top: 0;
left: 0;
box-sizing: border-box;
}
</style>
<body>
<!-- 容器:可视窗口 -->
<div id="container">
<!-- 数据展示列表 -->
<div id="list"></div>
<!-- 模拟滚动条 -->
<div id="placeholder"></div>
</div>
</body>
<script>
// 获取元素
let container = document.getElementById('container');
let list = document.getElementById('list');
let placeholder = document.getElementById('placeholder');
// 模拟数据多数据
let mockData = [];
for (let i = 0; i < 1000; i++) {
mockData.push({
label: `这是第${i}条数据`,
value: i
});
}
// 配置项
let config = {
itemHight: 30, // 每一项的高度
moreNum: 2, // 多渲染的数量
}
// 获取容器高度,计算渲染数量
let containerHeight = container.clientHeight;
// 计算渲染的item的数量
let allNum = Math.ceil(containerHeight/config.itemHight) + config.moreNum
// 设置模拟滚动条div的高度,让其等于总数据高度,以便出现正确的滚动条
placeholder.style.height = mockData.length * config.itemHight + 'px';
// 加了防抖的效果,性能更好
// container.addEventListener('scroll', debounce(resetPosition, 30))
// 不加防抖的效果
container.addEventListener('scroll', resetPosition)
resetPosition()
// 渲染函数
function resetPosition() {
// 获取容器的滚动高度,方便后面定位列表,使列表始终展示在视窗内
let scrollTop = container.scrollTop;
// 根据滚动高度计算开始和结束的index,从而截取对应的数据进行渲染
let startIndex = Math.floor(scrollTop / config.itemHight) // 开始index
let endIndex = startIndex + allNum; // 结束index = 开始index + 渲染数量
let renderData = mockData.slice(startIndex, endIndex); // 需要渲染的数据
// 将列表定位到可视窗口的位置
list.style.top = startIndex * config.itemHight + 'px';
// 渲染列表
let nodes = ''
renderData.forEach(item => {
nodes += `<div style="height: ${config.itemHight}px">${item.label}</div>`
})
list.innerHTML = nodes;
}
// 防抖函数
function debounce(fun, wait) {
let timeout
return function () {
clearTimeout(timeout)
timeout = setTimeout(() => {
fun.apply(this, arguments)
}, wait)
}
}
</script>
</html>