需求来源:
后端一次性返回大批量的数据(如:1000条),前端如果全部一次性渲染可能会导致页面卡顿,用户体验差,因此需要通过虚拟列表来实现。
实现思路:
1、包装一个容器代表可视区,根据设计图获取每条数据的高度,然后根据可视区的高度和每条数据的高度去计算一次可以渲染多少条数据。
2、设置容器的高度等于数据的条数*每条数据的高度
3、监听滚动条的滚动事件,获取每次滚动的scrollTop,然后除以元素的高度,得到每次获取数组中的数据下标,然后进行截取,再替换html的内容。
<!DOCTYPE html>
<html>
<head>
<title>js 实现虚拟列表</title>
</head>
<style>
html,
body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
ul,
li {
margin: 0;
padding: 0;
list-style: none;
}
.box {
width: 750px;
height: 500px;
overflow-y: auto;
box-sizing: border-box;
transform: translate(50%, 50%);
padding: 0;
}
li {
height: 40px;
background-color: #eee;
margin-bottom: 10px;
display: flex;
align-items: center;
padding-left: 20px;
border-radius: 5px;
box-sizing: border-box;
}
</style>
<body>
<!-- 可视区域的高度--用户看见的高度(750px) -->
<div class="box">
<!-- 内容真实高度 即200条数据的高度-->
<div class="content-area" id="contentArea">
<ul></ul>
</div>
</div>
<script>
let Element = document.querySelector('.box');
let itemHeight = 50; //每个元素的高度(li的高度40 + marginBottom 10)
let pageSize = Math.ceil(Element.clientHeight / itemHeight); // 每一页可以显示的元素个数(向上取整)
let data = [];
let startIndex = 0; //默认从第一条数据开始读取
// 初始化模拟数据
for (let i = 0; i < 50; i++) {
data.push(`列表${i + 1}`);
}
document.getElementById('contentArea').style.height = itemHeight * data.length + 'px'; // 占位dom的高度
// 默认加载第一页的数据
let html = '';
const sliceData = data.slice(startIndex, startIndex + pageSize);
for (let i = 0; i < sliceData.length; i++) {
html += `<li class="item">${sliceData[i]}</li>`;
}
Element.querySelector('#contentArea ul').innerHTML = html;
// 更新DOM
const updateHtml = () => {
let sliceData = data.slice(startIndex, startIndex + pageSize);
console.log('sliceData', sliceData);
const itemAll = Element.querySelectorAll('.item');
for (let i = 0; i < sliceData.length; i++) {
itemAll[i].innerHTML = sliceData[i];
}
};
// 滑动监听
Element.addEventListener('scroll', function () {
const scrollTop = Element.scrollTop; // 滚动高度
startIndex = Math.ceil(scrollTop / itemHeight); // 重新计算开始的下标,div顶部卷起来的长度除以列表元素的高度
updateHtml(); // 重新更新dom
Element.querySelector('#contentArea ul').style.transform =
`translateY(${startIndex * itemHeight}px)`;
});
</script>
</body>
</html>