一、为什么你的动画总是卡顿?
小明最近在开发一个电商网站的轮播图,使用setTimeout实现的动画总是出现轻微卡顿。当他改用requestAnimationFrame后,动画立即变得丝滑流畅。这背后究竟隐藏着怎样的浏览器运行机制?
二、requestAnimationFrame核心原理
1. 与浏览器渲染周期同步
浏览器以16.6ms(60Hz屏幕)为周期进行渲染:
// 传统做法(可能掉帧)
setInterval(() => {
更新动画
}, 16.6);
// RAF做法(自动同步)
function animate() {
更新动画
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
2. 智能节流机制
当页面处于后台或隐藏时,RAF会自动暂停执行,节省CPU资源。而setTimeout会持续运行,导致无谓的资源消耗。
三、六大实战应用场景
场景1:丝滑动画实现
function moveElement(element, distance) {
let start = null;
function step(timestamp) {
if (!start) start = timestamp;
const progress = timestamp - start;
// 计算移动距离(缓动函数)
const move = easeInOutQuad(progress, 0, distance, 1000);
element.style.transform = `translateX(${move}px)`;
if (progress < 1000) {
requestAnimationFrame(step);
}
}
requestAnimationFrame(step);
}
场景2:大数据量分批渲染
function renderLargeList(items) {
let index = 0;
function chunkRender() {
const start = performance.now();
// 每帧最多处理50ms的任务
while (index < items.length &&
performance.now() - start < 50) {
renderItem(items[index++]);
}
if (index < items.length) {
requestAnimationFrame(chunkRender);
}
}
chunkRender();
}
场景3:精准获取元素布局
function getStableLayout(element) {
return new Promise(resolve => {
requestAnimationFrame(() => {
const rect1 = element.getBoundingClientRect();
requestAnimationFrame(() => {
const rect2 = element.getBoundingClientRect();
rect1.top === rect2.top ? resolve(rect1) : getStableLayout(element);
});
});
});
}
四、性能优化三大法则
- 单帧时长控制:保持每帧任务在16ms内完成
- 避免强制同步布局:先读后写,批量操作
- 使用Web Worker分流计算:
// 主线程
const worker = new Worker('calc.js');
function animate() {
worker.postMessage(data);
requestAnimationFrame(animate);
}
// Worker线程
self.onmessage = ({data}) => {
const result = heavyCalculate(data);
self.postMessage(result);
}
五、高频面试题解析
Q1:requestAnimationFrame的执行时机是什么?
A:在浏览器重绘之前执行,与屏幕刷新率同步(通常16.6ms)
Q2:如何实现动画暂停/恢复?
let animationId = null;
let paused = false;
function toggleAnimation() {
if (paused) {
animationId = requestAnimationFrame(animate);
} else {
cancelAnimationFrame(animationId);
}
paused = !paused;
}
Q3:如何实现60FPS稳定动画?
A:计算时间差动态调整位移量:
let lastTime = 0;
function animate(timestamp) {
const delta = timestamp - lastTime;
const step = (delta / 16.6) * baseSpeed;
updatePosition(step);
lastTime = timestamp;
requestAnimationFrame(animate);
}
Q4:为什么RAF比setTimeout更适合做动画?
A:1自动匹配刷新率 2后台标签页暂停 3批量样式更新 4更高优先级
结合上面文章讲的事件循环机制负责处理 JavaScript 中的异步任务。它包含任务队列和调用栈,任务队列又分为宏任务队列和微任务队列。requestAnimationFrame 的回调函数会被插入到一个特殊的队列中,该队列会在每次重绘之前执行。这意味着,requestAnimationFrame 的回调函数会在合适的时机执行,避免不必要的重绘和回流,从而提升性能。