Canvas动画队列核心是状态驱动的逐帧调度,用requestAnimationFrame控制绘制节奏,以状态机管理动画项,分层按序绘制,并确保图像预加载完成后再启动。

Canvas图形动画队列的核心不是“排队画图”,而是**控制每一帧该画什么、何时画、按什么逻辑切换状态**。关键在于避免同步阻塞,把动画步骤拆解为可中断、可调度的单元,再用 requestAnimationFrame 驱动逐帧推进。
用状态机管理动画序列
每个动画任务应封装为带状态的对象:起始值、目标值、持续帧数、当前进度、完成回调。不要写 for 循环递增角度或位置,而是让每帧根据当前进度计算中间值。
- 定义动画项如:
{ type: 'rotate', target: 360, duration: 60, current: 0 } - 队列执行时,只更新当前项的
current++,并判断是否达到duration - 完成则调用其回调,移出队列,推入下一项
requestAnimationFrame 驱动主循环
主线程不能被阻塞,所有绘制必须放在 RAF 回调中。它天然匹配屏幕刷新节奏,且页面失焦时自动暂停,省电又稳定。
- 每次 RAF 触发时,先清空画布(
ctx.clearRect()),再遍历当前激活的动画项重绘 - 不依赖 setTimeout 的固定毫秒数,避免帧率抖动或跳帧
- 若某帧计算耗时略长,RAF 会自动跳过该帧,保持整体节奏不拖慢
分层绘制 + 显式顺序控制
多个图形依次出现,本质是控制它们的“可见性时间点”。不是等前一个画完再画下一个,而是在同一帧内按 z-order 或数组索引顺序绘制——谁在数组前面,就先 drawImage;谁 zIndex 高,就后画盖住前面。
- 维护一个图层配置数组:
[{ id: 'bg', showAt: 0 }, { id: 'char', showAt: 30 }, { id: 'ui', showAt: 60 }] - 主循环中,只绘制
showAt ≤ currentFrame的图层 - 绘制时严格按数组顺序调用
ctx.drawImage(),无需额外排序逻辑
预加载与就绪校验不可跳过
图像未加载完就调用 drawImage,结果是空白。所有图片必须提前加载完毕,并统一触发队列启动,否则顺序会错乱甚至中断。
- 用对象记录每张图的
Image实例和 loaded 状态 - 计数器累计成功加载数量,等于总数时才初始化动画队列
- 避免在单个
img.onload里直接 draw,防止“谁先加载完谁先闪一下”


















