应使用 requestAnimationFrame 实现 JS 动画,因其自动对齐屏幕刷新率、页面不可见时暂停省资源;正确做法是每次回调用 performance.now() 获取时间戳计算进度,避免手动估算帧间隔导致快慢不均。

JS 动画用 requestAnimationFrame 而不是 setTimeout 或 setInterval
直接用 setTimeout 控制样式变化,帧率不可控、容易掉帧,还可能和浏览器刷新节奏错开。而 requestAnimationFrame 会把动画逻辑交给浏览器调度,自动对齐屏幕刷新率(通常是 60fps),且在页面不可见时自动暂停,省资源。
常见错误是手动计算时间差做插值却忽略实际帧间隔,导致快慢不均。正确做法是每次回调都读取 performance.now() 获取高精度时间戳,再算出当前进度:
let startTime = 0;
function animate(timestamp) {
if (!startTime) startTime = timestamp;
const progress = Math.min((timestamp - startTime) / 1000, 1); // 持续 1 秒
element.style.transform = `translateX(${progress * 200}px)`;
if (progress < 1) requestAnimationFrame(animate);
}
requestAnimationFrame(animate);CSS 动画优先用 transform 和 opacity 属性
这两个属性触发的是合成(compositing)层更新,不触发重排(reflow)和重绘(repaint),GPU 可直接加速。一旦用了 left、top、width、height 或 background-color,就会强制走主线程布局+绘制流水线,卡顿明显。
注意点:
立即学习“Java免费学习笔记(深入)”;
-
will-change: transform可提前提示浏览器准备合成层,但别滥用——每个元素都加会导致内存浪费和层爆炸 - 用
@keyframes定义动画后,通过切换 class 触发,比内联 style 更易维护 - 动画结束后记得清理:设置
animation-fill-mode: forwards保持终态,或监听animationend事件做后续处理
性能对比关键看「是否触发 Layout / Paint」
CSS 动画本身不等于高性能——只有作用在可合成属性上、且没有频繁 JS 干预时才真正快。JS 动画如果只改 transform 和 opacity,并用 requestAnimationFrame 驱动,性能和纯 CSS 几乎无差别。
但真实场景中 JS 动画更灵活:
- 能响应用户交互实时调整动画参数(比如拖拽中跟随手指)
- 可基于滚动位置、视口变化等动态计算目标值
- 复杂缓动(如弹簧物理)用 CSS
timing-function很难精确表达
而 CSS 动画优势在于声明式、轻量、浏览器深度优化——适合固定路径、简单状态切换(如按钮 hover、菜单展开)。
混合使用时避免隐式重排和样式强制同步
最常被忽略的性能杀手:在 JS 动画帧里读取 offsetTop、getBoundingClientRect()、computedStyle 等会触发“强制同步布局”(forced synchronous layout),让浏览器立刻执行重排,打断渲染流水线。
解决方案:
- 把需要读取的布局信息提前一次性获取,缓存起来复用
- 改用
IntersectionObserver替代滚动中反复查元素位置 - 避免在
requestAnimationFrame回调里又去操作 DOM 样式后再读样式——写完就写完,别回头问
动画性能瓶颈往往不在“怎么动”,而在“动之前和动之中,JS 偷偷干了多少布局活”。











