浏览器每帧 16.67 ms,渲染之外常有"垃圾时间";
如何把埋点、预加载、长计算悄悄塞进这段空隙,而不卡动画?
requestIdleCallback 就是答案。
一、它到底是啥?
js
const id = requestIdleCallback(callback, { timeout: 1000 });
// id 用于 cancelIdleCallback(id);
- 作用 :把低优先级任务 排队,等浏览器主线程空闲时再执行。
- 返回 :任务 id,可
cancelIdleCallback(id)
取消。 - 兼容性:Chrome 47+、Edge 16+、Safari 14+;Firefox 55+。
二、API 速记
参数 | 说明 |
---|---|
callback |
函数,接收 IdleDeadline 对象 |
options.timeout |
最大等待毫秒,超时后强制执行 |
IdleDeadline
属性
timeRemaining()
:当前帧剩余时间(通常 0--50 ms)didTimeout
:是否因超时被触发
三、4 个可直接复制的实战场景
① 日志批量上报(不卡交互)
js
const logs = [];
function track(e) { logs.push(e); scheduleFlush(); }
let flushPending = false;
function scheduleFlush() {
if (flushPending) return;
flushPending = true;
requestIdleCallback(flushLogs, { timeout: 2000 });
}
function flushLogs(deadline) {
while (logs.length && deadline.timeRemaining() > 2) {
sendBatch(logs.splice(0, 5));
}
if (logs.length) requestIdleCallback(flushLogs, { timeout: 2000 });
else flushPending = false;
}
用户点击、滑动时日志先缓存,空闲时再上报 。
② 预加载低优先级资源
js
const lowRes = [
'/img/banner-2.jpg',
'/fonts/optional.woff2',
'/data/suggestions.json'
];
function prefetchOnIdle() {
let idx = 0;
function next(deadline) {
while (idx < lowRes.length && deadline.timeRemaining() > 0) {
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = lowRes[idx++];
document.head.appendChild(link);
}
if (idx < lowRes.length) requestIdleCallback(next);
}
requestIdleCallback(next);
}
prefetchOnIdle();
首屏渲染完成后再偷偷加载后续资源,提升后续页面速度 。
③ 大数据分片计算(不阻塞主线程)
js
function processLargeData(data, chunkSize) {
let offset = 0;
function processChunk(deadline) {
const start = performance.now();
while (offset < data.length && deadline.timeRemaining() > 0) {
processSlice(data.slice(offset, offset += chunkSize));
}
if (offset < data.length) requestIdleCallback(processChunk);
else console.log('✅ 处理完成');
}
requestIdleCallback(processChunk);
}
10 万条数据拆片,在主线程空闲时逐步处理 。
④ 计算 π 的蒙特卡洛方法(示例)
html
<button onclick="start()">开始计算</button>
<button onclick="stop()">停止</button>
<div id="pi">等待开始...</div>
<script>
let id, inside = 0, total = 0;
function step() {
const r = 10, x = Math.random() * 2 * r - r, y = Math.random() * 2 * r - r;
if (x * x + y * y < r * r) inside++;
total++;
document.getElementById('pi').textContent = (4 * inside / total).toFixed(6);
}
function work(deadline) {
while (deadline.timeRemaining() > 0) step();
id = requestIdleCallback(work);
}
function start() { id = requestIdleCallback(work); }
function stop() { cancelIdleCallback(id); }
</script>
每帧有剩余时间就计算一步,不卡顿动画 。
四、不适合的场景
场景 | 原因 |
---|---|
操作 DOM/更新 UI | 当前帧已绘制,改 DOM 会强制重排 |
长耗时任务 > 50 ms | 仍占主线程,需拆分或放 Worker |
紧急任务(输入/动画) | 应使用 requestAnimationFrame |
五、一句话总结
requestIdleCallback
= 主线程垃圾时间调度器 :把埋点、预加载、长计算塞进空闲帧,零阻塞、零依赖、零配置。