闽公网安备 35020302035485号


.渲染截断后的列表数据,进而实现无限加载
懒加载与虚拟列表其实都是延时加载的一种实现,原理相同但场景略有不同。懒加载的应用场景偏向于网络资源请求,解决网络资源请求过多时,造成的网站响应时间过长的问题。虚拟列表是对长列表渲染的一种优化,解决大量数据渲染时,造成的渲染性能瓶颈的问题。
let io = new IntersectionObserver(callback, option);callback 会触发两次。一次是目标元素刚刚进入视口(开始可见),另一次是完全离开视口(开始不可见)。
function getDataList() {
let data = []
for(let i = 0; i < 100000; i++) {
data.push({id: "item" + i, value: Math.random() * i})
}
return data;
}
2. Dom 创建及列表渲染:<ul class="container">
<span class="sentinels">....</span>
</ul>
function $(selector) {
return document.querySelector(selector)
}
function loadData(start, end) {
// 截取数据
let sliceData = getDataList().slice(start, end)
// 现代浏览器下,createDocumentFragment 和 createElement 的区别其实没有那么大
let fragment = document.createDocumentFragment();
for(let i = 0; i < sliceData.length; i++) {
let li = document.createElement('li');
li.innerText = JSON.stringify(sliceData[i])
fragment.appendChild(li);
}
$('.container').insertBefore(fragment, $('.sentinels'));
}
如果是基于框架,直接操作数据即可(Vue 伪代码):// 父组件
<virtual-list :listData="listData"></virtual-list>
// 子组件
<ul class='container'>
<li
v-for="item in sliceData"
:key="item.id"
>{{ item }}</li>
</ul>
...
// js
this.sliceData = this.data.slice(start, index)
3. 使用 intersectionObserver API 创建监听器:let count = Math.ceil(document.body.clientHeight / 120);
let startIndex = 0;
let endIndex = 0;
...
let io = new IntersectionObserver(function(entries) {
loadData(startIndex, count)
// 堆代码 duidaima.com
// 标志位元素进入视口
if(entries[0].isIntersecting) {
// 更新列表数据起始和结束位置
startIndex = startIndex += count;
endIndex = startIndex + count;
if(endIndex >= getDataList().length) {
// 数据加载完取消观察
io.unobserve(entries[0].target)
}
// requestAnimationFrame 由系统决定回调函数的执行时机
requestAnimationFrame(() => {
loadData(startIndex, endIndex)
let num = Number(getDataList().length - startIndex)
let info = ['还有', num , '条数据']
$('.top').innerText = info.join(' ')
if(num - count <= 0) {
$('.top').classList.add('out')
}
})
}
});
// 开始观察“标志位”元素
io.observe($('.sentinels'));
})
由于 IntersectionObserver 无法监听动态创建的 dom,所以我们设置一个「标志位」元素 span.sentinels 作为监听的目标对象。<ul class="container"> <span class="sentinels">....</span> </ul>如果目标元素正处于交叉状态 entries[0].isIntersecting == true,则代表 .sentinels 进入了可视区域,从而加载新的列表数据。
if(entries[0].isIntersecting) {
...
requestAnimationFrame(() => {
loadData(startIndex, endIndex)
})
...
}
最后将新的列表 insertBefore 到其前面,进而实现无限加载。$('.container').insertBefore(fragment, $('.sentinels'));