You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Canvas动态FPS动画适配:基于requestAnimationFrame统一多FPS动画时长

嘿,这个问题抓得很准!在Canvas里用requestAnimationFrame做动画时,靠帧数来控时长绝对是个坑——毕竟30fps和60fps的设备,相同帧数下耗时差一倍,根本没法同步结束。核心解法其实很简单:抛弃帧数计数,改用时间戳来驱动动画进度。下面我给你一步步讲清楚怎么实现:

核心逻辑:用时间进度替代帧数

requestAnimationFrame的回调函数会自动传入一个高精度时间戳(DOMHighResTimeStamp),这个时间戳是从页面加载开始计算的毫秒数,精度能到微秒级。我们要做的就是:

  1. 记录动画的开始时间
  2. 每帧计算已流逝的时间总时长的比例
  3. 用这个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

火山引擎 最新活动