如何使用RequestAnimationFrame在保持60fps刷新率时放慢动画?
理解RequestAnimationFrame与慢动画实现
我完全理解这种困惑——很多人刚接触requestAnimationFrame(rAF)时,都会把“渲染帧率”和“动画速度”混为一谈。先帮你把rAF的核心机制掰明白,再给你一个简单的方案,既能保持Canvas 60fps的流畅渲染,又能轻松控制动画快慢。
先搞懂RequestAnimationFrame的工作逻辑
rAF不是一个固定间隔的定时器,它的核心是和浏览器的屏幕刷新同步:
- 浏览器每次准备刷新屏幕前(通常是60次/秒,也就是每~16.67ms一次),会调用你传给rAF的回调函数。
- 它会自动在页面隐藏时暂停执行,避免浪费资源,这是
setInterval做不到的。 - 回调函数会接收一个关键参数:
timestamp,这是一个高精度的时间戳(从页面加载开始计算的毫秒数),用来计算两次回调之间的时间差。
你之前可能误以为要通过“减少rAF的调用次数”来放慢动画,但这会直接破坏和屏幕的同步,导致掉帧、卡顿,完全没必要——我们要分开控制渲染帧率(保持60fps,让画面流畅)和动画速度(让元素动得慢一些)。
正确实现:用时间差控制动画速度
核心思路是:动画的变化量基于时间,而不是rAF的调用次数。不管rAF的调用间隔有没有微小波动(比如偶尔因为主线程繁忙,间隔变成17ms),动画的速度都会保持稳定,同时渲染依然跟着屏幕刷新走,保持60fps。
举个具体的例子,假设你原来的代码是每次rAF就让元素移动固定像素:
let x = 0; function animate() { x += 1; // 每次移动1px ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillRect(x, 50, 50, 50); requestAnimationFrame(animate); } requestAnimationFrame(animate);
改成基于时间差的版本后,就能轻松控制速度:
let x = 0; let lastTimestamp = 0; const moveSpeed = 50; // 元素每秒移动50px(基础速度) const speedScale = 0.5; // 动画放慢到50%,想更快就改大,比如2就是两倍速 function animate(timestamp) { // 初始化上一次时间戳 if (!lastTimestamp) lastTimestamp = timestamp; // 计算两次回调的时间差,转换成秒(方便计算每秒移动的距离) const deltaTime = (timestamp - lastTimestamp) / 1000; lastTimestamp = timestamp; // 基于时间差计算本次要移动的距离:速度 × 时间 × 缩放因子 x += moveSpeed * deltaTime * speedScale; // 正常执行Canvas绘制 ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillRect(x, 50, 50, 50); // 继续请求下一次渲染 requestAnimationFrame(animate); } requestAnimationFrame(animate);
关键点解释:
- deltaTime:两次rAF回调之间的时间差,确保动画速度不受浏览器临时卡顿的影响,始终稳定。
- speedScale:这就是你要的“放慢动画”的开关——把它改成0.3就是30%速度,改成2就是两倍速,完全不影响渲染帧率。
- 保持60fps:rAF依然会跟着屏幕刷新频率调用,每次回调都执行一次绘制,所以画面依然是每秒60帧的流畅状态,只是元素每次绘制时的位移变小了,看起来动画变慢了。
总结一下
- 不要试图修改rAF的调用间隔来控制动画速度,这会破坏和屏幕的同步,导致卡顿。
- 用rAF提供的
timestamp计算时间差,基于时间来驱动动画变化,这是专业动画的标准做法。 - 放慢/加快动画只需要调整速度参数或缩放因子,渲染帧率会自动保持和屏幕同步(通常60fps)。
内容的提问来源于stack exchange,提问作者PiotrZadka
相关产品推荐
相关产品推荐

