Canvas动态FPS动画适配:基于requestAnimationFrame统一多FPS动画时长
嘿,这个问题抓得很准!在Canvas里用requestAnimationFrame做动画时,靠帧数来控时长绝对是个坑——毕竟30fps和60fps的设备,相同帧数下耗时差一倍,根本没法同步结束。核心解法其实很简单:抛弃帧数计数,改用时间戳来驱动动画进度。下面我给你一步步讲清楚怎么实现:
核心逻辑:用时间进度替代帧数
requestAnimationFrame的回调函数会自动传入一个高精度时间戳(DOMHighResTimeStamp),这个时间戳是从页面加载开始计算的毫秒数,精度能到微秒级。我们要做的就是:
- 记录动画的开始时间
- 每帧计算已流逝的时间占总时长的比例
- 用这个0~1的比例来更新动画元素的状态
不管设备是30fps还是60fps,只要时间到了总时长,进度就会到1,动画同步结束。
具体实现步骤与代码示例
1. 定义动画配置
先确定你的动画总时长(比如5秒,也就是5000毫秒),然后初始化必要的变量:
const canvas = document.getElementById('myCanvas'); const ctx = canvas.getContext('2d'); // 动画总时长:5秒 const TOTAL_DURATION = 5000; // 记录动画开始时间 let animationStartTime = null;
2. 编写动画回调函数
在requestAnimationFrame的回调里,基于时间戳计算进度,然后更新动画:
function animate(currentTimestamp) { // 第一次执行时,初始化开始时间 if (!animationStartTime) { animationStartTime = currentTimestamp; } // 计算已流逝时间和进度比例(0~1) const elapsedTime = currentTimestamp - animationStartTime; const progress = Math.min(elapsedTime / TOTAL_DURATION, 1); // 清屏准备绘制下一帧 ctx.clearRect(0, 0, canvas.width, canvas.height); // 基于进度更新动画元素:以移动矩形为例 // 矩形从左(x=0)移动到右(x=canvas.width-50),宽度50 const rectX = 0 + (canvas.width - 50) * progress; ctx.fillStyle = '#2ecc71'; ctx.fillRect(rectX, canvas.height/2 - 25, 50, 50); // 进度未到1时,继续请求下一帧 if (progress < 1) { requestAnimationFrame(animate); } else { console.log('动画已结束!'); } }
3. 启动动画
最后调用requestAnimationFrame启动动画即可:
// 触发动画 requestAnimationFrame(animate);
额外优化技巧
- 暂停/继续功能:如果需要支持暂停,可以记录暂停时的
elapsedTime,继续时用当前时间戳减去暂停时长,重新设置animationStartTime。 - 高精度时间:推荐用
performance.now()替代Date.now(),前者精度更高,适合动画这种对时间敏感的场景。 - 防止进度溢出:用
Math.min(progress, 1)确保进度不会超过1,避免动画结束后元素还继续变化。
内容的提问来源于stack exchange,提问作者user6287012




