requestAnimationFrame
一、核心概念解析
定义:requestAnimationFrame
(rAF) 是浏览器提供的原生 API,用于在下一次重绘前执行动画更新,确保动画与浏览器的绘制周期同步。
设计目标:
- 🎯 解决
setTimeout
/setInterval
动画的卡顿、掉帧问题 - ⚡ 优化性能,减少不必要的渲染
- 🔋 页面不可见时自动暂停(节省资源)
二、核心特性与优势
特性 | 说明 | 优势 |
---|---|---|
帧率同步 | 自动匹配显示器刷新率(60Hz=16.6ms/帧) | 流畅无撕裂 |
智能休眠 | 页面隐藏时自动暂停 | 节省 CPU/GPU 资源 |
精确时间控制 | 回调接收高精度时间戳(DOMHighResTimeStamp ) |
精确计算动画进度 |
批量执行 | 同一帧中所有 rAF 回调集中处理 | 避免布局抖动 |
渲染前执行 | 在浏览器执行样式计算和布局之后,绘制之前 | 保证最新状态渲染 |
三、基础使用模式
let startTime;
let animationId;
function animate(timestamp) {
// 初始化时间基准
if (!startTime) startTime = timestamp;
// 计算动画进度(0-1)
const progress = Math.min((timestamp - startTime) / 2000, 1);
// 更新动画状态
element.style.transform = `translateX(${progress * 500}px)`;
// 继续动画循环
if (progress < 1) {
animationId = requestAnimationFrame(animate);
}
}
// 启动动画
animationId = requestAnimationFrame(animate);
// 停止动画
function stopAnimation() {
cancelAnimationFrame(animationId);
}
四、与 setTimeout 的对比
特性 | requestAnimationFrame | setTimeout |
---|---|---|
执行时机 | 下一帧渲染前 | 延迟时间后(不可靠) |
刷新率适配 | 自动匹配屏幕刷新率 | 固定时间间隔 |
隐藏页面处理 | 自动暂停 | 持续执行 |
性能影响 | 低(浏览器优化) | 易导致布局抖动 |
时间精度 | 微秒级(performance.now() ) |
毫秒级 |
函数调用 | 每帧集中执行所有回调 | 独立定时器 |
五、最佳实践与高级技巧
1. 动画生命周期管理
class AnimationController {
constructor() {
this.rafId = null;
this.isRunning = false;
}
start(callback) {
if (this.isRunning) return;
this.isRunning = true;
const step = (timestamp) => {
if (!this.isRunning) return;
callback(timestamp);
this.rafId = requestAnimationFrame(step);
};
this.rafId = requestAnimationFrame(step);
}
stop() {
this.isRunning = false;
cancelAnimationFrame(this.rafId);
}
}
2. FPS 控制(节流渲染)
const targetFPS = 30;
let lastTime = 0;
function throttledUpdate(timestamp) {
if (timestamp - lastTime > 1000 / targetFPS) {
// 执行渲染逻辑
renderScene();
lastTime = timestamp;
}
requestAnimationFrame(throttledUpdate);
}
requestAnimationFrame(throttledUpdate);
3. 滚动性能优化
let ticking = false;
window.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(() => {
updatePosition(); // DOM操作
ticking = false;
});
ticking = true;
}
});
六、性能优化场景
1. Canvas 动画优化
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制逻辑
drawParticles();
requestAnimationFrame(draw);
}
requestAnimationFrame(draw);
2. 复杂计算分帧执行
const heavyTasks = [...Array(1000)].map((_, i) => () => processItem(i));
function runTasks(timestamp) {
const start = performance.now();
while (heavyTasks.length > 0 && performance.now() - start < 8) {
heavyTasks.shift()();
}
if (heavyTasks.length > 0) {
requestAnimationFrame(runTasks);
}
}
requestAnimationFrame(runTasks);
七、面试高频问题
1. 为什么 rAF 比 setTimeout 更适合动画?
答:rAF 与浏览器渲染周期同步,自动适应刷新率,在页面隐藏时暂停执行,避免不必要的计算和布局抖动,提供更流畅的动画体验。
2. 如何实现动画暂停/继续功能?
let paused = false;
let animationId;
function animate(timestamp) {
if (paused) return;
// 动画逻辑
updateAnimation(timestamp);
animationId = requestAnimationFrame(animate);
}
function togglePause() {
paused = !paused;
if (!paused) {
animationId = requestAnimationFrame(animate);
}
}
3. rAF 回调函数参数的作用?
答:参数是
DOMHighResTimeStamp
类型的时间戳,表示回调被触发的时间(精确到微秒)。用于:
- 计算动画的精确时长
- 实现与时间相关的动画效果
- 性能监控和帧率计算
4. 如何检测当前帧率?
let lastTime = performance.now();
let frameCount = 0;
function detectFPS(timestamp) {
frameCount++;
if (timestamp >= lastTime + 1000) {
console.log(`FPS: ${frameCount}`);
frameCount = 0;
lastTime = timestamp;
}
requestAnimationFrame(detectFPS);
}
requestAnimationFrame(detectFPS);
5. rAF 与微任务的执行顺序?
答:
- 事件处理
- 微任务(Promise、MutationObserver)
- requestAnimationFrame
- 样式计算/布局/绘制
- requestIdleCallback
八、常见错误与陷阱
- 递归调用缺失:忘记在回调中重新调用 rAF
- 阻塞渲染:单次回调执行超过 16ms
// 错误:长时间阻塞 function animate() { heavyCalculation(); // 耗时>16ms requestAnimationFrame(animate); }
- 布局抖动:在 rAF 中混用读写操作
// 错误:强制同步布局 function animate() { element.style.width = '100px'; // 写 const width = element.offsetWidth; // 读(触发重排)· requestAnimationFrame(animate); }
内存泄漏:未在组件卸载时取消 rAF
// React 示例 useEffect(() => { let id; const animate = () => { /* ... */ id = requestAnimationFrame(animate); }; id = requestAnimationFrame(animate); return () => cancelAnimationFrame(id); }, []);
九、面试回答策略
1. 原理优先
"rAF 的核心价值在于它将动画执行与浏览器的渲染管道同步。浏览器每帧执行:
- JS 执行 → 2. 样式计算 → 3. 布局 → 4. 绘制 rAF 回调在步骤 1 末尾执行,确保动画更新在渲染前完成"
2. 性能数据支撑
"在我们的电商首页轮播图优化中:
- 使用 setTimeout:平均 FPS 45,CPU 占用率 38%
- 改用 rAF 后:FPS 稳定 60,CPU 占用降至 22%"
3. 框架整合展示
// Vue 组合式 API
import { onMounted, onUnmounted } from 'vue';
export function useAnimation(callback) {
let rafId;
const start = () => {
const step = (timestamp) => {
callback(timestamp);
rafId = requestAnimationFrame(step);
};
rafId = requestAnimationFrame(step);
};
onMounted(start);
onUnmounted(() => cancelAnimationFrame(rafId));
return { start };
}
4. 高级场景应对
"在 VR 展示项目中,我们处理 120Hz 高刷屏:
- 使用
window.matchMedia('(dynamic-range: high)')
检测高刷屏- 动态调整 rAF 中的物理计算步长
- 使用
performance.now()
精确控制时间增量"💡 面试金句:
"requestAnimationFrame 是浏览器赋予开发者的动画节拍器——它让我们的代码与显示器的刷新保持完美共振,将生硬的机械运动转化为丝滑的视觉体验。掌握它,就掌握了高性能动画的脉搏。"