1. 时间切片
不要一次性渲染所有数据,使用requestAnimationFrame循环插入数据
window.requestAnimationFrame(function(){
for(let i = 0; i < pageCount; i++){
let li = document.createElement('li');
li.innerText = curIndex + i + ' : ' + ~~(Math.random() * total)
ul.appendChild(li)
}
loop(curTotal - pageCount,curIndex + pageCount)
})
并且可以使用DocumentFragment
DocumentFragment,文档片段接口,表示一个没有父级文件的最小文档对象。它被作为一个轻量版的Document使用,用于存储已排好版的或尚未打理好格式的XML片段。最大的区别是因为DocumentFragment不是真实DOM树的一部分,它的变化不会触发DOM树的(重新渲染) ,且不会导致性能等问题。 可以使用document.createDocumentFragment方法或者构造函数来创建一个空的DocumentFragment
window.requestAnimationFrame(function(){
let fragment = document.createDocumentFragment();
for(let i = 0; i < pageCount; i++){
let li = document.createElement('li');
li.innerText = curIndex + i + ' : ' + ~~(Math.random() * total)
fragment.appendChild(li)
}
ul.appendChild(fragment)
loop(curTotal - pageCount,curIndex + pageCount)
})
此时先创建很多DocumentFragment,然后一次性插入到ul中。而不是在之前的循环中插入很多次。
2. 虚拟列表
虚拟列表的实现,实际上就是在首屏加载的时候,只加载可视区域内需要的列表项,当滚动发生时,动态通过计算获得可视区域内的列表项,并将非可视区域内存在的列表项删除。
- infinite-list-container 为可视区域的容器,
- infinite-list-phantom 为容器内的占位,高度为总列表高度,用于形成滚动条
- infinite-list 为列表项的渲染区域,transform会动态计算当前高度,并修改位置
真实数据visibleData,是不断地计算startendtime,然后从listData中截取的。
<template>
<div ref="list" class="infinite-list-container" @scroll="scrollEvent($event)">
<div class="infinite-list-phantom" :style="{ height: listHeight + 'px' }"></div>
<div class="infinite-list" :style="{ transform: getTransform }">
<div ref="items"
class="infinite-list-item"
v-for="item in visibleData"
:key="item.id"
:style="{ height: itemSize + 'px',lineHeight: itemSize + 'px' }"
>{{ item.value }}</div>
</div>
</div>
</template>
export default {
name:'VirtualList',
props: {
//所有列表数据
listData:{
type:Array,
default:()=>[]
},
//每项高度
itemSize: {
type: Number,
default:200
}
},
computed:{
//列表总高度
listHeight(){
return this.listData.length * this.itemSize;
},
//可显示的列表项数
visibleCount(){
return Math.ceil(this.screenHeight / this.itemSize)
},
//偏移量对应的style
getTransform(){
return `translate3d(0,${this.startOffset}px,0)`;
},
//获取真实显示列表数据
visibleData(){
return this.listData.slice(this.start, Math.min(this.end,this.listData.length));
}
},
mounted() {
this.screenHeight = this.$el.clientHeight;
this.start = 0;
this.end = this.start + this.visibleCount;
},
data() {
return {
//可视区域高度
screenHeight:0,
//偏移量
startOffset:0,
//起始索引
start:0,
//结束索引
end:null,
};
},
methods: {
scrollEvent() {
//当前滚动位置
let scrollTop = this.$refs.list.scrollTop;
//此时的开始索引
this.start = Math.floor(scrollTop / this.itemSize);
//此时的结束索引
this.end = this.start + this.visibleCount;
//此时的偏移量
this.startOffset = scrollTop - (scrollTop % this.itemSize);
}
}
};
<style scoped>
.infinite-list-container {
height: 100%;
overflow: auto;
position: relative;
-webkit-overflow-scrolling: touch;
}
.infinite-list-phantom {
position: absolute;
left: 0;
top: 0;
right: 0;
z-index: -1;
}
.infinite-list {
left: 0;
right: 0;
top: 0;
position: absolute;
text-align: center;
}
.infinite-list-item {
padding: 10px;
color: #555;
box-sizing: border-box;
border-bottom: 1px solid #999;
}
</style>
动态高度列表
以预估高度先行渲染,然后获取真实高度并缓存 通过positions数组,维护每个item的高度
设置缓冲区
props: {
//缓冲区比例
bufferScale:{
type:Number,
default:1
}
}
可视区上方&&下方渲染条数aboveCount&&belowCount获取方式如下:
aboveCount(){
return Math.min(this.start,this.bufferScale * this.visibleCount)
}
belowCount(){
return Math.min(this.listData.length - this.end,this.bufferScale * this.visibleCount);
}
真实渲染数据visibleData获取方式如下:
visibleData(){
let start = this.start - this.aboveCount;
let end = this.end + this.belowCount;
return this._listData.slice(start, end);
}