应优先使用 requestAnimationFrame 替代 setTimeout/setInterval 实现动画,因其对齐屏幕刷新率、避免掉帧;配合 CSS transform/opacity、避免强制同步布局、合理使用 Canvas 分层与脏矩形重绘,并持续通过 DevTools 分析渲染性能。

用 requestAnimationFrame 替代 setTimeout 或 setInterval
浏览器对 requestAnimationFrame 有原生调度优化,会自动对齐屏幕刷新率(通常是 60fps),而 setTimeout 的执行时机不可控,容易掉帧或错位。尤其在动画密集或页面后台运行时,setInterval 可能累积大量未执行回调,造成卡顿或突跳。
- 始终把动画逻辑封装进
requestAnimationFrame回调中,不要嵌套多层setTimeout - 手动控制动画启停:调用
cancelAnimationFrame清理上一帧 ID,避免内存泄漏 - 如果需要节流(比如只在滚动时触发部分动画),仍可用
requestAnimationFrame包裹节流逻辑,而非退回到定时器
let animationId;
function animate() {
// 更新样式或 Canvas 绘图
updatePosition();
renderFrame();
animationId = requestAnimationFrame(animate);
}
// 启动
animationId = requestAnimationFrame(animate);
// 停止(例如离开视口时)
cancelAnimationFrame(animationId);
优先使用 CSS 动画和 transform + opacity
CSS 动画由浏览器渲染线程直接处理,且当只变更 transform(如 translate3d、scale)或 opacity 时,可触发 GPU 加速,不触发布局(Layout)和重绘(Paint),性能远高于修改 left、top 或 width 等属性。
- 避免在 JS 动画中频繁设置
element.style.left,改用element.style.transform = 'translateX(100px)' - 强制硬件加速:加
transform: translateZ(0)或will-change: transform(但仅对长期动画元素谨慎使用,避免过度触发图层分裂) - 用
@keyframes+animation实现可预测的循环动画,比 JS 控制更省资源
避免强制同步布局(Forced Synchronous Layout)
在 JS 动画循环中,若先读取布局信息(如 offsetTop、getBoundingClientRect()),再立即修改样式,浏览器会立刻触发回流(Reflow)来返回准确值——这会打断渲染流水线,严重拖慢帧率。
- 把所有“读取”操作集中到帧开始,所有“写入”操作集中到帧末尾(即 RAF 回调内分两阶段)
- 用
getComputedStyle代替offsetWidth等易触发回流的属性(但仍需谨慎,它也可能触发) - 对滚动驱动型动画(如视差),缓存滚动位置,不要每帧都调用
window.scrollY
Canvas 动画注意清除与脏矩形重绘
全量清屏(ctx.clearRect(0, 0, width, height))在高分辨率或大画布下开销显著;而反复绘制未变化区域也会浪费 GPU 时间。
立即学习“前端免费学习笔记(深入)”;
- 只清除实际发生变化的矩形区域:
ctx.clearRect(x, y, w, h),尤其适用于局部动画(如粒子、文字光标) - 对静止背景,可分离为多个
层(底图层不动,前景层每帧重绘) - 避免在动画循环中重复创建路径对象或字体样式;复用
Path2D、预设ctx.font
const backgroundCanvas = document.getElementById('bg');
const foregroundCanvas = document.getElementById('fg');
const bgCtx = backgroundCanvas.getContext('2d');
const fgCtx = foregroundCanvas.getContext('2d');
// 背景只初始化一次
drawStaticBackground(bgCtx);
function animate() {
// 只清空并重绘前景区域
fgCtx.clearRect(0, 0, fgCanvas.width, fgCanvas.height);
drawMovingObjects(fgCtx);
requestAnimationFrame(animate);
}
复杂动画往往卡在“看不见”的地方:一次意外的 offsetHeight 读取、一个没清理的 requestAnimationFrame、或者 will-change 被滥用导致图层爆炸。性能优化不是堆技巧,而是持续观察 DevTools 的 Rendering 面板里每一帧的 Layout / Paint / Composite 耗时。











