requestAnimationFrame是浏览器原生的动画调度机制,按屏幕刷新率自动执行、支持节流与暂停,应结合时间戳计算delta time实现帧率无关的流畅动画,并需手动cancelAnimationFrame管理生命周期。

用 requestAnimationFrame(简称 rAF)实现流畅动画,核心是让浏览器按自身刷新节奏(通常是 60fps)自动调度绘制,而不是靠 setTimeout 或 setInterval “硬控”时间间隔。它能自动适配屏幕刷新率、在页面不可见时暂停执行、减少丢帧,是 Web 动画的推荐方式。
基本用法:递归调用 + 时间戳控制
rAF 接收一个回调函数,该函数会在下一次重绘前被调用,并传入一个高精度时间戳(DOMHighResTimeStamp),单位为毫秒,精确到微秒级。要持续动画,需在回调中再次调用 rAF:
function animate(timestamp) {
// 计算本次动画应到达的状态(如位置、透明度)
// 通常基于起始值、目标值、总时长和已耗时间
update(timestamp);
render();
requestAnimationFrame(animate); // 继续下一帧
}
requestAnimationFrame(animate); // 启动注意:不要在回调里写死 16ms 延迟——rAF 已由浏览器优化,强行加延时反而破坏同步。
用时间戳做匀速/缓动动画的关键
直接用帧数计数(如“第 1 帧、第 2 帧”)会导致动画速度受帧率影响:设备卡顿时变慢,高性能设备可能超速。正确做法是用时间差(delta time)驱动逻辑:
立即学习“Java免费学习笔记(深入)”;
- 记录上一帧的时间戳
- 当前帧中计算
delta = timestamp - lastTimestamp - 位移量 = 速度 × delta(单位:px/ms),或用于缓动函数的进度比
t = Math.min((timestamp - startTime) / duration, 1)
例如实现 2 秒内从 0px 移动到 400px 的线性动画:
const startTime = performance.now();
const duration = 2000; // ms
const startPos = 0;
const endPos = 400;
<p>function animate(timestamp) {
const elapsed = timestamp - startTime;
const progress = Math.min(elapsed / duration, 1);
const currentPos = startPos + (endPos - startPos) * progress;</p><p>element.style.transform = <code>translateX(${currentPos}px)</code>;</p><p>if (progress < 1) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);避免常见坑:取消动画 & 性能优化
rAF 不会自动停止,必须手动控制生命周期,否则造成内存泄漏或无效计算:
- 用变量保存当前 rAF 的 ID(
let animationId = requestAnimationFrame(...)),需要暂停/终止时调用cancelAnimationFrame(animationId) - 动画元素离开视口或组件卸载时,务必取消 —— 尤其在 React/Vue 等框架中容易遗漏
- 只在状态真正变化时更新 DOM;批量读写分离:先读(如
getBoundingClientRect),再算,最后统一写(如改style.transform),避免强制同步布局(layout thrashing) - 优先使用 CSS transform 和 opacity 做动画,它们可触发 GPU 加速,且不触发重排(reflow)
封装一个可复用的动画控制器
把启动、暂停、销毁逻辑收拢,提升可维护性:
class SimpleAnimation {
constructor(callback, options = {}) {
this.callback = callback;
this.duration = options.duration || Infinity;
this.startTime = null;
this.id = null;
}
<p>start() {
this.startTime = performance.now();
this.tick();
}</p><p>tick = (timestamp) => {
if (!this.startTime) this.startTime = timestamp;
const elapsed = timestamp - this.startTime;
const progress = this.duration === Infinity
? 1
: Math.min(elapsed / this.duration, 1);</p><pre class="brush:php;toolbar:false;">this.callback(progress, elapsed);
if (progress < 1) {
this.id = requestAnimationFrame(this.tick);
}};
stop() { if (this.id) { cancelAnimationFrame(this.id); this.id = null; } } }
// 使用 const anim = new SimpleAnimation( (p) => element.style.opacity = p, { duration: 1000 } ); anim.start();
不复杂但容易忽略:rAF 是浏览器原生节流机制,不是“更高级的 setTimeout”。用对了,动画自然跟手;用错了,比如嵌套调用、未清理、混用 style 操作,照样卡顿。


















