是什么:
虚拟列表:通过计算滚动视窗,每次只渲染部分元素,既减少了首屏压力,长时间加载也不会有更多的性能负担。
为什么:
大量的 DOM 元素会使得我们的网页非常“重”。当 DOM 元素超过 1500 至 2000 个的时候,页面就开始又延迟,尤其是在小型的、性能差的设备上尤为明显。
想象一下,有一个无线滚动的页面,你不断的下拉,它实际上可能形成了上万个 DOM 元素,每个元素还包含子节点,这样将消耗巨大的性能。
Virtual scrollers 正是来解决这个问题的。
怎么做:
思路:写好承接的ul - 获取数据 - 设定滚动框(itemList)高度 - 初始化显示框内容 - 监听滚动框滚动事件 - 滚动高度%每项高度来计算第一条展示的index - 判断是否还是之前的index来判断是否更新DOM - 更新DOM - 用第一个index和计算的显示框可容纳条数更新对应数据的列表DOM
js原生写法:
<!DOCTYPE html>
<html>
<head>
<title>javascript 虚拟列表</title>
</head>
<style>
html,
body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
font-size: 14px;
line-height: 1.6;
font-family: '微软雅黑';
}
ul,
li {
margin: 0;
padding: 0;
list-style: none;
}
.itemList {
width: 100%;
max-width: 750px;
height: 100%;
overflow-y: auto;
padding: 10px 10px 0 10px;
box-sizing: border-box;
margin: 0 auto;
}
.itemList .itemArea {
overflow: hidden;
}
.itemList ul {
width: 100%;
}
.itemList ul li {
width: 100%;
height: 100px;
background-color: #f8f8f8;
margin-bottom: 10px;
}
.itemList ul li .ibox {
width: 150px;
height: 100%;
background-color: #eee;
float: left;
margin-right: 10px;
display: flex;
display: -webkit-flex;
align-items: center;
justify-content: center;
}
.itemList ul li .content {
padding: 10px 10px 10px 0;
}
.itemList ul li .content h3 {
font-size: 18px;
margin: 0;
}
.itemList ul li .content p {
font-size: 12px;
}
</style>
<body>
<div class="itemList">
<div class="itemArea">
<ul>
</ul>
</div>
</div>
<script>
class List {
constructor(options) {
this.el = document.querySelector(options.el);
this.data = options.data || [];
this.itemHeight = options.itemHeight;
this.startIndex = options.startIndex || 0;
}
// 初始化数据
getData() {
for (let i = 0; i < 200; i++) {
this.data.push({
pic: `图片${i+1}`,
title: `标题${i+1}`,
content: `内容${i+1}`
})
}
}
// 更新DOM
updateHtml() {
let itemAll = this.el.querySelectorAll(".item");
for (let i = this.startIndex, j = 0, len = this.pageSize + this.startIndex; i < len && i < this.data
.length; i++, j++) {
itemAll[j].querySelector(".ibox").innerHTML = this.data[i].pic;
itemAll[j].querySelector(".content h3").innerHTML = this.data[i].title;
itemAll[j].querySelector(".content p").innerHTML = this.data[i].content;
}
}
// 滚动处理
handleScroller() {
return () => {
let scrollTop = this.el.scrollTop; // 滚动高度
let fixedScrollTop = scrollTop - scrollTop % this.itemHeight; // 内容区域Y轴偏移,确保
let startIndex = Number.parseInt(scrollTop / this.itemHeight);
console.log(startIndex)
// 当开始位置有变化,则执行
if (this.startIndex != startIndex) {
this.startIndex = startIndex
this.el.querySelector(".itemArea ul").style.transform = 'translateY(' + fixedScrollTop +
'px)';
this.updateHtml();
}
}
}
default () {
let html = "";
for (let i = this.startIndex, j = 0, len = this.pageSize + this.startIndex; i < len && i < this.data
.length; i++, j++) {
html += `
<li class="item">
<div class="ibox">${this.data[i].pic}</div>
<section class="content">
<h3>${this.data[i].title}</h3>
<p>${this.data[i].content}</p>
</section>
</li>`;
}
this.el.querySelector(".itemArea ul").innerHTML = html;
// 内容高度
this.el.children[0].style.height = this.itemHeight * this.data.length + 'px';
}
// 当窗口改变
resize() {
window.onresize = () => {
this.pageSize = Math.ceil(
this.el.offsetHeight / this.itemHeight
);
if (this.startIndex + this.pageSize >= this.data.length) {
this.startIndex = 0;
this.el.querySelector(".itemArea ul").style.transform = 'translateY(0px)';
}
this.default()
}
}
// 初始化
init() {
// 初始化数据
if (this.data.length < 1) this.getData();
// 获取一个滚动屏 最大可容纳 几个 子元素
this.pageSize = Math.ceil(
this.el.offsetHeight / this.itemHeight
) + 1;
// 初始化节点
this.default()
// Scroll监听
this.el.addEventListener('scroll', this.handleScroller.call(this), false);
// Window resize
this.resize();
}
}
/**
* class List
*
* el - DOM对象
* data - 初始化数据
* itemHeight - 列表子集元素高度
* startIndex - 起始数据下标
*
*/
let list = new List({
el: ".itemList",
data: [],
itemHeight: 110,
startIndex: 0
})
list.init();
</script>
</body>
</html>
vue写法
可使用组件 - vue-virtual-scroll-list