requestIdleCallback优化长列表时为什么还是会卡顿?
我用虚拟列表渲染上千条数据时,尝试用requestIdleCallback分批渲染,但滚动到后面还是明显卡顿。之前试过分页和简单的节流函数都没彻底解决,现在这样写:
function renderBatch() {
requestIdleCallback(() => {
for(let i = 0; i < 50; i++) {
// 批量更新DOM操作
}
requestIdleCallback(renderBatch);
});
}
renderBatch();
这样递归调用会不会有问题?感觉每次回调都在排队执行,滚动时CPU占用反而更高了,该怎么调整批次大小和触发时机?
首先,增加每个批次处理的数据量,减少requestIdleCallback的调用次数。比如一次处理100条或者更多,看具体性能表现调整。
其次,确保在每次requestIdleCallback中只做必要的DOM操作。可以先构建好要插入的DOM片段,然后一次性插入到页面中,这样能减少重排和重绘的次数。
最后,考虑使用Intersection Observer来监听可见区域的变化,只渲染用户能看到的部分,这样能大大减少需要渲染的数据量。
以下是改进后的代码示例:
pre class="pure-highlightjs line-numbers">
function renderBatch(startIndex) {
requestIdleCallback(deadline => {
let shouldYield = false;
let endIndex = startIndex + 100; // 每次处理100条
const fragment = document.createDocumentFragment();
while (startIndex < endIndex && !shouldYield) {
// 假设data是你的数据源
const item = data[startIndex];
const node = createNode(item); // 根据item创建DOM节点的方法
fragment.appendChild(node);
startIndex++;
shouldYield = deadline.timeRemaining() < 1; // 检查是否有剩余时间
}
document.getElementById('container').appendChild(fragment);
if (startIndex < data.length) {
requestIdleCallback(() => renderBatch(startIndex));
}
});
}
function createNode(item) {
// 创建并返回一个DOM节点的逻辑
const node = document.createElement('div');
node.textContent = item.text;
return node;
}
renderBatch(0);
记得根据实际情况调整每次处理的数据量和DOM更新的方式,找到最适合你应用的方案。
正确的做法是结合节流和帧率控制,在每次空闲时只处理一小批,同时根据当前帧的剩余时间动态调整批量数量,而不是固定 50 条。你可以用 deadline.timeRemaining() 判断还有多少空闲时间,比如:
另外记得转义 DOM 操作,别直接频繁操作真实节点,可以用文档片段(DocumentFragment)或离屏 DOM 预渲染,减少重排。还有,虚拟列表本身更适合用 IntersectionObserver 监听可视区域,而不是靠 requestIdleCallback 做主渲染逻辑。这个 API 更适合做优先级较低的预加载或清理工作。你现在这情况,建议先切回 requestAnimationFrame + 分帧渲染,控制每帧不超过 16ms,会更稳。