应优先使用 requestAnimationFrame 替代 setTimeout/setInterval 实现动画,因其能对齐屏幕刷新率、避免掉帧;需避免布局抖动、慎用 will-change、优先 CSS 合成动画,并优化 Canvas 绘制性能。

用 requestAnimationFrame 替代 setTimeout 或 setInterval
浏览器对 requestAnimationFrame 有专门调度优化,能自动对齐屏幕刷新率(通常是 60fps),而 setTimeout 的执行时机不可控,容易掉帧甚至累积延迟。
常见错误是写成这样:
setInterval(() => {
element.style.transform = `translateX(${x}px)`;
}, 16);
应该改为:
function animate() {
x += 2;
element.style.transform = `translateX(${x}px)`;
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
- 不要在
requestAnimationFrame回调里做 DOM 查询(如getBoundingClientRect())、样式读取(如offsetTop)或强制同步布局,否则触发重排(reflow) - 如果动画逻辑复杂,可先计算状态,再统一写入样式
- 注意节流:不需要每帧都更新时,可用计数器跳过部分帧
CSS 动画优先于 JS 操作 style
纯 CSS 动画(@keyframes + animation)由合成器线程处理,不阻塞主线程,比 JS 频繁修改 style.transform 更稳。
立即学习“前端免费学习笔记(深入)”;
适用场景包括:循环动画、入场/出场动效、状态切换过渡。
- 确保只使用可合成的属性:
transform和opacity,避免触发动画期间的重排(如left、top、width) - 加
will-change: transform可提前提示浏览器提升图层,但别滥用——长期开启会增加内存开销 - 用
animation-play-state: paused控制暂停,比反复启停requestAnimationFrame更轻量
避免 layout thrashing(布局抖动)
当 JS 在同一帧内交替读写布局相关属性(比如先读 offsetHeight,再改 style.height),浏览器被迫同步计算样式和布局,严重拖慢动画帧率。
- 把所有读操作集中到帧开头,所有写操作集中到帧末尾
- 用
getComputedStyle替代offsetXXX等可能触发重排的属性(但仍属读操作,别混在写中间) - 考虑用
ResizeObserver替代轮询offsetWidth来响应尺寸变化 - Chrome DevTools 的 “Rendering” 面板勾选 “Layout Shift Regions” 和 “FPS Meter”,能直观看到抖动和掉帧
Canvas 动画卡顿时先查绘制路径与缓存
Canvas 卡顿很少是因为 requestAnimationFrame 本身,多源于重复绘制未裁剪区域、频繁创建临时对象或未启用离屏缓存。
- 用
ctx.save()/ctx.restore()替代重复设置 fillStyle/strokeStyle - 静态内容提前绘入
OffscreenCanvas或canvas缓存,动画中只 blit - 避免在动画循环里调用
ctx.drawImage绘制未预加载的图片——会触发同步解码 - 用
ctx.setTransform(1, 0, 0, 1, 0, 0)重置变换矩阵,比ctx.resetTransform()兼容性更好(IE/旧 Edge 不支持后者)











