Canvas关键帧动画本质是JavaScript模拟:按升序time戳定义state对象,通过requestAnimationFrame驱动插值计算与分离渲染;需预加载帧资源、合理清屏以兼顾性能与正确性。

Canvas 本身不支持关键帧概念,所谓“带关键帧的逐帧动画”,本质是用 JavaScript 模拟关键帧逻辑:在特定时间点设定目标状态(位置、缩放、旋转等),再通过插值或分段函数生成中间帧,最后由 requestAnimationFrame 驱动绘制。它不是 CSS 或专业动画工具里的可视化关键帧轨道,而是代码级的状态控制与时间映射。
关键帧数据结构需明确时间戳与属性值
每个关键帧应至少包含两个核心字段:
-
time:以毫秒为单位的绝对时间偏移(如 0、300、800),不是帧序号;建议统一用
performance.now()起始时间做基准,避免系统时钟漂移 -
state:一个对象,记录该时刻所有受控属性值,例如
{ x: 100, y: 200, scale: 1.2, rotation: 0.5 }
关键帧必须按 time 升序排列;动画播放器根据当前已过时间,在相邻两帧间线性插值(或使用贝塞尔缓动函数)计算出实时 state。
播放器需分离时间推进与像素渲染
不能把插值计算和 ctx.drawImage() 写进同一个循环里。推荐结构:
- 主循环用
requestAnimationFrame获取高精度时间戳 - 独立函数
getCurrentState(t)接收当前时间t,返回插值后的完整 state 对象 - 绘制函数
render(state)只负责将 state 映射到 canvas 像素:比如用ctx.setTransform()应用位移+缩放+旋转,再 drawImage
这样设计后,变速、倒播、跳转到任意时间点都只需修改输入时间 t,无需重绘资源或重建逻辑。
逐帧素材需预加载并缓存解码结果
如果动画依赖图像序列(如 PNG 帧组),关键帧播放不能容忍加载延迟:
- 所有帧图片必须在播放前完成
img.onload,且建议调用createImageBitmap(img)预解码,避免首次绘制时卡顿 - 若关键帧对应的是精灵图中的某一块区域,要提前算好
sx/sy/sw/sh并缓存,不要每次 render 时重复计算 - 对同一帧多次复用(如暂停时持续显示),可将
ctx.drawImage()封装为带缓存的绘制单元,避免重复裁剪开销
清屏策略取决于关键帧是否覆盖全画布
关键帧动画常伴随局部运动,全屏 clearRect 可能浪费性能:
- 若所有关键帧只改变一个角色的位置,且背景静态,可只清上一帧矩形区域 + 当前帧目标区域,再分别重绘
- 若关键帧含透明度变化或叠加模式(如
globalCompositeOperation = 'lighter'),则不应清屏,而靠合成逻辑自然淡化旧内容 - 最稳妥方式仍是每帧
clearRect(0, 0, canvas.width, canvas.height)—— 现代浏览器下耗时通常低于 0.1ms,视觉正确性优先于微秒级优化


















