大量のデータを高性能でレンダリング
大量のデータのレンダリングには、タイム スライスと仮想リスト処理を使用できます
タイムスライス
簡単な理解は、データを多くの部分に分割し、それらをバッチでレンダリングすることです。ページ ジャンクは、多数の DOM を同時にレンダリングするために利用可能なタイム スライスが原因です。
方法 1 :setTimeout
短所: ただし、ページをすばやくスクロールすると、ページにスプラッシュ スクリーンまたは白いスクリーンがあることがわかります。
//需要插入的容器
let ul = document.getElementById('container');
// 插入十万条数据
let total = 100000;
// 一次插入 20 条
let once = 20;
//总页数
let page = total/once
//每条记录的索引
let index = 0;
//循环加载数据
function loop(curTotal,curIndex){
if(curTotal <= 0){
return false;
}
//每页多少条
let pageCount = Math.min(curTotal , once);
setTimeout(()=>{
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)
},0)
}
loop(total,index);
方法 2 :requestAnimationFrame + DocumentFragment
//需要插入的容器
let ul = document.getElementById('container');
// 插入十万条数据
let total = 100000;
// 一次插入 20 条
let once = 20;
//总页数
let page = total/once
//每条记录的索引
let index = 0;
//循环加载数据
function loop(curTotal,curIndex){
if(curTotal <= 0){
return false;
}
//每页多少条
let pageCount = Math.min(curTotal , once);
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)
})
}
loop(total,index);
仮想リスト
仮想リストの実現は、実際には最初の画面がロードされたときに可視領域に必要なリスト項目のみをロードすることです. スクロールが発生すると、可視領域のリスト項目は計算によって動的に取得され、非可視領域のメモリのリスト項目が削除されます。
1. 現在の表示可能領域の開始データ インデックス (startIndex) を計算します。
2. 現在の表示可能領域の終了データ インデックス (endIndex) を計算します。
3. 現在の表示可能領域のデータを計算し、ページにレンダリングします
。リスト全体のオフセット位置 startOffset 内の startIndex に対応するデータをリストに設定します
<!-- 虚拟列表 -->
<div class="box">
<!-- 内容真实高度 即200条数据的高度-->
<div class="content-area" id="contentArea">
<ul></ul>
</div>
</div>
let el = document.querySelector(".box");
let itemHeight = 110; //每个元素的高度(li的高度100 + marginBottom 10)
let pageSize = Math.ceil(el.clientHeight / itemHeight); // 获取一个滚动屏最大可容纳子元素个数(向上取整)
let data = []; //mock数据
let startIndex = 0; //可视区第一行下标
let endIndex = pageSize; //可视区最后一行下标
// 初始化模拟数据
let getData = () => {
for (let i = 0; i < 200; i++) {
data.push({
content: `我是显示的内容${
i + 1}`,
});
}
};
// 加载数据并插入到dom页面
let loadData = () => {
let html = "";
let sliceData = data.slice(startIndex, endIndex);
for (let i = 0; i < sliceData.length; i++) {
html += `
<li class="item">
<p>${
sliceData[i].content}</p>
</li>`;
}
el.querySelector("#contentArea ul").innerHTML = html;
};
// 更新DOM
let updateHtml = () => {
let sliceData = data.slice(startIndex, endIndex);
let itemAll = el.querySelectorAll(".item");
for (let i = 0; i < sliceData.length; i++) {
itemAll[i].querySelector("p").innerHTML = sliceData[i].content;
}
};
// 滑动监听
el.addEventListener("scroll", function () {
let scrollTop = el.scrollTop; // 滚动高度
startIndex = Math.ceil(scrollTop / itemHeight); // 重新计算开始的下标,div顶部卷起来的长度除以列表元素的高度
endIndex = startIndex + pageSize;
updateHtml(); // 重新更新dom
el.querySelector("#contentArea ul").style.transform =
"translateY(" + startIndex * itemHeight + "px)";
});
let init = () => {
getData();
loadData();
document.getElementById("contentArea").style.height =
itemHeight * data.length + "px"; // 占位dom的高度
};
// 页面初始化调用
init();
遅延読み込み
あまり紹介せず、1 文の説明: 最初はすべてのデータがレンダリングされず、ビューに表示されているデータのみが表示され、ページの下部までスクロールすると、さらにデータが読み込まれます
実装原理:親要素のスクロールイベントをリッスンすることはもちろん、IntersectionObserverやgetBoundingClientRectなどのAPIで実装することも可能
ただし、スクロールイベントが頻繁に発生するため、手動で調整する必要があります; スクロール要素には DOM が多く、フリーズしやすいため、IntersectionObserver を使用することをお勧めします