requestAnimationFrame 是最流畅可控的动画方式,因其与屏幕刷新同步、自动适配不同刷新率、页面不可见时暂停且支持精确控制;应仅操作 transform 和 opacity 等合成属性,避免重排重绘,并慎用与 CSS transition 的混合。

直接用 requestAnimationFrame 配合 CSS 变换(transform、opacity 等)是最流畅、最可控的动画实现方式,比 setTimeout 或 CSS @keyframes 更适合需要动态响应或逐帧逻辑的场景。
为什么不用 setTimeout 或 setInterval 做动画
它们不与屏幕刷新同步,容易掉帧、卡顿,且无法自动适配不同设备的刷新率(如 60Hz / 90Hz / 120Hz)。requestAnimationFrame 会把回调塞进浏览器下一帧的绘制队列,天然对齐显示器刷新节奏。
- 即使你设
setTimeout(..., 16),实际执行时间可能漂移,尤其在页面后台运行或 CPU 负载高时 -
requestAnimationFrame在页面不可见时自动暂停,省电又合理 - 它返回一个
frameId,可随时用cancelAnimationFrame(frameId)中止,控制力更强
如何用 requestAnimationFrame 驱动 CSS 动画
核心思路:不操作 style.left/top 这类触发重排的属性,只改 transform 和 opacity,并用 JS 控制动画进度,再通过 element.style.transform 同步到 DOM。
let start = null;
const duration = 300; // 毫秒
const element = document.querySelector('.box');
function animate(timestamp) {
if (!start) start = timestamp;
const progress = Math.min((timestamp - start) / duration, 1);
// 使用 transform 替代 left/top
element.style.transform = translateX(${progress * 200}px);
element.style.opacity = 1 - progress;
if (progress < 1) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
立即学习“Java免费学习笔记(深入)”;
- 务必用
transform和opacity—— 它们由合成器线程处理,不触发 layout/paint,性能最好 - 避免在动画帧里读取
offsetLeft、getBoundingClientRect()等会强制同步布局的 API - 如果需要 easing,直接在
progress上套函数,比如cubic-bezier(0.25, 0.46, 0.45, 0.94)对应的实现,而非依赖 CSStransition-timing-function
和 CSS transition 混用的风险
JS 修改 style.transform 的同时,元素还挂着 transition: transform 300ms?这会导致行为不可控:过渡可能被中断、叠加、甚至产生“回弹”。
- 要么全 JS 控制(删掉所有
transition),要么全 CSS 控制(用classList.add('animate')触发@keyframes) - 若必须混合(例如初始状态靠 CSS,后续靠 JS),记得在 JS 动画开始前清除 transition:
element.style.transition = 'none';,动画第一帧后再恢复 - 注意
will-change: transform可提前提示浏览器优化,但别滥用——它会常驻图层,增加内存开销
真正难的不是写几行 requestAnimationFrame,而是判断该不该用它:简单入场/退出动画,transition 更轻量;需要拖拽跟随、滚动视差、物理模拟,才值得上 JS 帧控。别为了“高级感”绕远路。











