Canvas渲染机制的核心是基于栈的状态机模型,它通过save()和restore()保存并恢复变换矩阵、裁剪路径、样式及文本阴影等全部上下文属性快照,实现绘制状态的精确隔离与切换。

Canvas 渲染机制的核心不在“画了什么”,而在“怎么记住当前能画什么”——状态机模型正是它管理绘图行为的底层逻辑。它不保存图形对象,也不维护图层树,而是用一个栈式结构精确记录每次绘制前的上下文快照。
状态栈是Canvas的“记忆中枢”
Canvas 2D 上下文(CanvasRenderingContext2D)内部维护一个**状态栈(state stack)**,每个入栈状态包含全部影响绘制结果的属性:
- 变换矩阵(
translate/rotate/scale等累积效果) - 裁剪路径(
clip()定义的可见区域) - 样式配置(
fillStyle、strokeStyle、lineWidth、globalAlpha等) - 文本与阴影设置(
font、textAlign、shadowBlur等) - 图像平滑开关(
imageSmoothingEnabled)
这些属性共同构成“当前可绘制状态”。只要没调用 save(),所有后续绘制都基于这个动态叠加的状态;而一旦调用 restore(),就不是“撤销上一步”,而是**弹出并完全还原到上一个 save 时的整套属性快照**。
save/restore 不是操作历史,而是快照切换
很多人误以为 save() 是记录“操作”,其实它只保存属性值,不记录任何绘制动作。例如:
ctx.fillStyle = 'blue'; ctx.fillRect(0, 0, 10, 10); // 蓝色方块 ctx.save(); // 记录:fillStyle=blue,无变换,无裁剪... ctx.fillStyle = 'red'; ctx.translate(20, 0); ctx.fillRect(0, 0, 10, 10); // 红色方块,偏移后绘制 ctx.restore(); // 立即恢复:fillStyle=blue,变换重置,裁剪清空 ctx.fillRect(0, 20, 10, 10); // 又一个蓝色方块,位置不受之前 translate 影响
注意:restore() 后,不仅颜色恢复,连坐标系也回到原点——因为变换矩阵被整体替换,不是简单地“反向平移”。
状态机让局部隔离成为可能
在复杂图形中(如嵌套图标、带阴影的文字、旋转的仪表盘),不同元素常需互不干扰的样式与坐标系。状态栈天然支持这种“作用域隔离”:
- 每个组件绘制前
save(),绘制后restore(),确保不影响外部状态 - 避免手动重置几十个属性(比如改完
globalAlpha还得记着恢复shadowColor) - 配合
clip()使用时,能安全限制子区域渲染范围,且不会污染父区域裁剪
没有状态栈,开发者就得自己维护所有上下文变量,极易出错;有了它,Canvas 就像拥有了轻量级的“绘图作用域”。
性能提示:save/restore 有开销,但不可省略
save() 和 restore() 涉及对象拷贝与栈操作,在高频动画中频繁调用会影响帧率。但它们的代价远小于手动重置全部属性或重复计算变换矩阵。真正该优化的是:
- 避免在单帧内无谓地
save()多次(比如循环里每次都 save) - 用
setTransform(1,0,0,1,0,0)替代restore()重置变换(仅当确定只需清变换) - 对静态内容提前缓存为
OffscreenCanvas,减少主画布状态切换频次
状态机不是性能瓶颈的根源,而是 Canvas 正确性与可维护性的基础设施。











