应使用 requestAnimationFrame 实现 JS 动画,因其自动对齐屏幕刷新率、页面不可见时暂停省资源;正确做法是每次回调用 performance.now() 获取时间戳计算进度,避免手动估算帧间隔导致快慢不均。
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);transform 和 opacity 属性这两个属性触发的是合成(compositing)层更新,不触发重排(reflow)和重绘(repaint),GPU 可直接加速。一旦用了 left、top、width、height 或 background-color,就会强制走主线程布局+绘制流水线,卡顿明显。
注意点:
will-change: transform 可提前提示浏览器准备合成层,但别滥用——每个元素都加会导致内存浪费和层爆炸@keyframes 定义动画后,通过切换 class 触发,比内联 style 更易维护animation-fill-mode: forwards 保持终态,或监听 animationend 事件做后续处理CSS 动画本身不等于高性能——只有作用在可合成属性上、且没有频繁 JS 干预时才真正快。JS 动画如果只改 transform 和 opacity,并用 requestAnimationFrame 驱动,性能和纯 CSS 几乎无差别。
但真实场景中 JS 动画更灵活:
timing-function 很难精确表达而 CSS 动画优势在于声明式、轻量、浏览器深度优化——适合固定路径、简单状态切换(如按钮 hover、菜单展开)。
最常被忽略的性能杀手:在 JS 动画帧里读取 offsetTop、getBoundingClientRect()、computedStyle 等会触发“强制同步布局”(forced synchronous layout),让浏览器立刻执行重排,打断渲染流水线。
解决方案:
IntersectionObserver 替代滚动中反复查元素位置requestAnimationFrame 回调里又去操作 DOM 样式后再读样式——写完就写完,别回头问动画性能瓶颈往往不在“怎么动”,而在“动之前和动之中,JS 偷偷干了多少布局活”。