requestAnimationFrame 比 setTimeout 更适合动画,因其自动对齐屏幕刷新率(如60Hz),避免丢帧卡顿;setTimeout 时机不可控,易累积误差、触发重排抖动,后台或高负载时掉帧明显;且 requestAnimationFrame 在标签页不可见时自动暂停,节省资源。

requestAnimationFrame 为什么比 setTimeout 更适合动画
因为 requestAnimationFrame 会自动对齐屏幕刷新率(通常是 60Hz),避免丢帧和卡顿;而 setTimeout 的执行时机不可控,容易累积误差、触发重排抖动,尤其在页面后台或 CPU 压力大时掉帧明显。
浏览器还可能在标签页不可见时暂停 requestAnimationFrame,节省资源——这是默认优化,不用手动处理。
- 它不接受毫秒参数,只传一个时间戳(
DOMHighResTimeStamp),单位是毫秒但精度可达微秒级 - 必须在每次回调里主动调用下一次
requestAnimationFrame,否则动画只执行一帧 - 不要在回调里做大量计算或 DOM 写操作,否则拖慢渲染帧率
最简可用的 requestAnimationFrame 动画循环
下面这段代码让一个 function animate(timestamp) {
if (!start) start = timestamp;
const progress = Math.min((timestamp - start) / duration, 1);
box.style.transform = if (progress < 1) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate); 注意: 立即学习“Java免费学习笔记(深入)”; 直接线性运动生硬。要加缓动,只需把 然后替换原逻辑中的 真正难的不是启停逻辑,而是状态同步:DOM 位置、JS 进度变量、动画时间轴三者必须始终一致,稍有错位就会跳变。这点很容易被忽略。let start = null;
const box = document.getElementById('box');
const duration = 2000; // 动画总时长 2s
translateX(${progress * 400}px);timestamp 是高精度时间戳,不是 Date.now();progress 用 Math.min 防止超调;动画结束时不继续调用 requestAnimationFrame,避免无谓循环。如何实现 easing 缓动效果
progress 输入进一个缓动函数,输出调整后的进度值。比如 ease-in-out:function easeInOutCubic(t) {
return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
}progress:const eased = easeInOutCubic(progress);
box.style.transform = `translateX(${eased * 400}px)`;
ts-easing 或直接抄 easings.net 的 JS 版本transition 也支持 cubic-bezier(),但 JS 控制更适合需要动态中断、变速或联动的场景动画中途暂停、取消和重新开始的正确姿势
requestAnimationFrame 本身没有暂停机制,靠控制是否继续调用实现。关键是要保存当前状态(位置、起始时间、是否暂停):
let animationId = null 存储当前帧 ID,调用 cancelAnimationFrame(animationId) 中断lastTimestamp 和当前 progress,恢复时用新 timestamp 减去已耗时,算出剩余时间requestAnimationFrame,等用户点击“继续”才启动











