需求:渲染十万条数据,每条数据的内容是一个序号。
方案一:一次渲染
const ul = document.getElementById("ul");
const count = 100000;
const start = new Date().getTime();
for (let index = 0; index < count; index++) {
const tmpDom = document.createElement("li");
tmpDom.innerHTML = Math.random() * 100;
ul.appendChild(tmpDom);
}
const jsEndTime = new Date().getTime() - start;
console.log("js execute time:", jsEndTime);
setTimeout(fn() => {
const allTime = new Date().getTime() - start;
console.log("allTime:", allTime);
}, 0);
执行过程:
- script执行,打印
js execute time: 491ms
; - GUI渲染线程开始工作;
- 调用setTimeout回调,打印
jallTime: 4182ms
大部分时间用于渲染了,白屏时间太长。
方案二:setTimeout分批渲染
setTimeout分批渲染策略,每次渲染一批数据,分多次渲染完成。由于浏览器每16.6ms渲染一次,所以我们可以设置setTimeout延时时间为16ms左右。
const ul = document.getElementById("root");
let total = 10000;
let once = 20;
let pageCount = total / once;
function loop(page) {
if (page >= pageCount) {
return;
}
setTimeout(function () {
for (let index = 0; index < once; index++) {
const tmpDom = document.createElement("li");
tmpDom.innerHTML = page * once + index;
ul.appendChild(tmpDom);
}
page++;
loop(page);
}, 16);
}
loop(0);
执行过程:
- script执行,setTimeout 回调进入事件队列;
- 10ms后执行setTimeout 回调,再次将setTimeout 回调进入事件队列;
- 如果渲染时间片到了执行渲染线程;
- 重复步骤2,3
此方案容易导致掉帧或闪频的出现,当setTimeout的步调和重绘的频率不一致可能会一次渲染多页的数据。setTimeout的步调和重绘的频率不一致的原因:
- 由于不同设备刷新频率不同,setTimeout的延时时间也要设置不同,否则导致步调不一致;
- setTimeout回调的时间不确定,当执行栈中的任务执行完成后才有可能去检查事件队列中是否有任务要执行。
方案三:requestAnimationFrame分批渲染
使用setTimeout分批渲染方案容易导致dom操作和重绘的频率不一致引起闪屏,如果保证重绘前能够执行dom操作那么dom操作和重绘的频率就一致了,就解决了闪屏的问题。requestAnimationFrame就是因此诞生的。
const ul = document.getElementById("root");
let total = 10000;
let pageSize = 20;
let pageCount = total / pageSize;
function loop(page) {
if (page >= pageCount) {
return;
}
window.requestAnimationFrame(function () {
for (let index = 0; index < pageSize; index++) {
const tmpDom = document.createElement("li");
tmpDom.innerHTML = page * pageSize + index;
ul.appendChild(tmpDom);
}
page++;
loop(page);
});
}
loop(0);
执行过程:
- script执行,requestAnimationFrame回调下次重绘前调用;
- 如果渲染时间片到了执行requestAnimationFrame回调,将requestAnimationFrame回调下次重绘前调用,渲染;
- 重复步骤2。
总结
- 一次大量dom操作,渲染时间较长会导致页面卡顿;
- requestAnimationFrame在浏览器重绘前会调用指定的回调。
参考
- https://juejin.im/post/6844903938894872589#heading-1