深入解析 requestAnimationFrame
和 requestIdleCallback
requestAnimationFrame (rAF)
和 requestIdleCallback (rIC)
都是浏览器提供的 异步调度 API,但它们的执行时机和用途完全不同。
API | 主要用途 | 何时执行 | 是否保证执行 | 适合场景 |
---|---|---|---|---|
requestAnimationFrame |
高优先级 UI 更新 | 下一帧渲染前(通常 16.67ms 内) | ✅ 一定执行(下一帧到来时) | 动画、滚动、WebSocket 实时数据可视化 |
requestIdleCallback |
低优先级任务 | 浏览器空闲时(时间不确定) | ❌ 不一定执行(如果浏览器一直忙) | 预加载、日志统计、AI 计算、非关键数据处理 |
1. requestAnimationFrame (rAF)
:下一帧渲染前执行
🔹 工作原理
浏览器的 渲染帧率 一般是 60Hz ,即 每 16.67ms 需要完成一次绘制(包括 JS 计算、样式计算、布局、绘制等)。
-
requestAnimationFrame
会在下一帧开始前执行 ,保证代码在下一帧渲染前完成,不会阻塞 UI。 -
适用于 高频 UI 更新,比如:
- CSS 动画
- 页面滚动
- WebSocket 高频推送的数据可视化
- Canvas 绘图
🔹 示例 1:流畅动画
ini
let box = document.getElementById("box");
let position = 0;
function move() {
position += 5;
box.style.transform = `translateX(${position}px)`;
if (position < 300) {
requestAnimationFrame(move); // 递归调用,持续更新
}
}
requestAnimationFrame(move);
✅ 为什么使用 requestAnimationFrame
?
- 不卡顿:会在合适的时间点执行,不会阻塞主线程。
- 不影响页面性能:浏览器空闲时自动暂停,节省资源。
🔹 示例 2:WebSocket 高频推送数据
ini
let pending = false;
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
if (!pending) {
pending = true;
requestAnimationFrame(() => {
updateUI(data);
pending = false;
});
}
};
function updateUI(data) {
document.getElementById("output").innerText = JSON.stringify(data);
}
📌 避免 UI 频繁更新,导致卡顿
- 如果 WebSocket 每 1ms 推送一次数据,直接更新 DOM 会让页面卡死。
- 使用
requestAnimationFrame
让 UI 更新保持在 60FPS 以内,保证流畅度。
🔹 示例 3:滚动优化
监听 scroll
事件时,直接操作 DOM 可能导致页面卡顿,应该用 requestAnimationFrame
优化:
ini
let ticking = false;
window.addEventListener("scroll", () => {
if (!ticking) {
requestAnimationFrame(() => {
updateScrollPosition(window.scrollY);
ticking = false;
});
ticking = true;
}
});
function updateScrollPosition(scrollY) {
document.getElementById("position").innerText = scrollY;
}
📌 好处 :避免 scroll
事件触发过于频繁,优化滚动时的 UI 更新。
2. requestIdleCallback (rIC)
:浏览器空闲时执行
🔹 工作原理
-
requestIdleCallback
让浏览器在 主线程空闲时 执行低优先级任务。 -
执行时间不固定,取决于浏览器的空闲情况:
- 如果用户在快速滚动 ,可能不会触发
requestIdleCallback
。 - 如果 CPU 负载高,可能很久都不会执行。
- 如果用户在快速滚动 ,可能不会触发
🔹 示例 1:处理后台任务
scss
function heavyTask(deadline) {
while (deadline.timeRemaining() > 0) {
console.log("执行低优先级任务...");
}
requestIdleCallback(heavyTask);
}
requestIdleCallback(heavyTask);
📌 deadline.timeRemaining()
返回本次空闲时间的剩余毫秒数,避免影响 UI 交互。
🔹 示例 2:懒加载数据
当页面加载完毕后,在空闲时间预加载一些不重要的数据:
javascript
requestIdleCallback(() => {
fetch("/api/data").then(res => res.json()).then(data => {
console.log("预加载数据完成:", data);
});
});
📌 好处:不影响页面主线程,提高用户体验。
🔹 示例 3:日志上报
scss
requestIdleCallback(() => {
sendAnalyticsData();
});
📌 好处:浏览器空闲时再上传日志,避免影响用户体验。
3. requestAnimationFrame
vs requestIdleCallback
:何时用哪一个?
场景 | 用 requestAnimationFrame |
用 requestIdleCallback |
---|---|---|
动画 | ✅ 需要流畅动画 | ❌ 不能保证执行时间 |
高频 WebSocket 数据 | ✅ 控制 UI 更新频率 | ❌ 可能无法及时更新 |
页面滚动 | ✅ 监听 scroll 事件 |
❌ 不能保证执行时机 |
后台任务(日志、数据计算) | ❌ 影响 UI 体验 | ✅ 浏览器空闲时执行 |
预加载数据 | ❌ 影响用户交互 | ✅ 在用户空闲时加载 |
AI 计算 | ❌ 主线程压力大 | ✅ 只有空闲时才执行 |
4. 如何组合使用?
🔹 WebSocket 高速数据流 + UI 更新优化
ini
let pending = false;
let backgroundData = [];
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
backgroundData.push(data);
// 让 UI 更新不超过 60FPS
if (!pending) {
pending = true;
requestAnimationFrame(() => {
updateUI(backgroundData);
backgroundData = [];
pending = false;
});
}
// 在浏览器空闲时处理日志
requestIdleCallback(() => {
processLogs(backgroundData);
});
};
function updateUI(data) {
console.log("更新 UI", data);
}
function processLogs(data) {
console.log("后台处理数据", data);
}
📌 优化点:
requestAnimationFrame
让 UI 更新流畅,不会因 WebSocket 高频推送导致页面卡死。requestIdleCallback
在浏览器空闲时处理日志,避免影响 UI 渲染。
5. 总结
requestAnimationFrame
:UI 渲染优化 ,适用于 动画、滚动、WebSocket UI 更新。requestIdleCallback
:后台任务调度 ,适用于 日志、预加载、AI 计算。- 两者结合使用,可以优化高性能应用,提升页面流畅度和用户体验! 🚀