Canvas旋转动画真正平滑的关键是用performance.now()时间戳驱动,计算归一化进度progress=(currentTime-startTime)/duration,再经缓动函数(如easeInOutQuad)映射为角度。

Canvas 旋转动画要真正平滑,核心不是“每帧转多少度”,而是让每一帧的旋转角度严格对应当前真实经过的时间——用时间戳驱动,而非帧数或循环计数器。
用 performance.now() 做时间基准
起始时刻记录一次 performance.now(),后续每一帧都读取当前时间戳,两者相减得到已过毫秒数。这个差值是唯一可信的时间源,不受帧率波动影响。
- 避免用
Date.now()(可能被系统时钟调整干扰) - 不要用
setInterval或手动i++计数,它们和真实时间脱钩 - 若动画需同步外部时间(如视频 currentTime),直接读该值,不另起计时器
把时间映射为旋转进度 progress
将已过时间除以总周期时长,得到归一化进度值 progress ∈ [0, 1]。它才是控制图形状态的“主变量”。
- 例如:2 秒一圈 →
progress = (currentTime - startTime) / 2000 - 用
Math.min(progress, 1)防止超限;用progress % 1实现无缝循环 - 所有角度计算都基于它:
angle = startAngle + (endAngle - startAngle) * easing(progress)
选对缓动函数,转动才自然
线性插值(progress 直接用)会让指针启停生硬。加缓动后,加速度变化符合人眼感知习惯。
- 推荐
easeInOutQuad:起步慢、中间快、收尾缓 - 简单实现:
function easeInOutQuad(t) { return t - 秒针动画尤其依赖毫秒级精度,缓动+时间戳组合可彻底消除“跳秒”感
渲染必须绑定 requestAnimationFrame
每一帧回调里,按“算进度 → 更新状态 → 清空重绘”顺序执行,确保视觉始终反映此刻该有的样子。
- 清空画布后立即重绘全部动态元素,避免残留或闪烁
- 复杂场景可用离屏 canvas 预绘制静态部分,只在主 canvas 更新旋转对象
- 交互跳转时间点(如拖动旋转角度滑块)时,立刻用新时间重算
progress和角度,并在下一帧完整呈现


















