restore() 抛 domexception 的本质是状态栈为空时调用,即 save() 次数少于 restore(),常见于循环中无条件多次 restore、分支逻辑漏配对或异步误用。

save() 和 restore() 不保存像素,只保存“怎么画”的设置;用错配对或嵌套过深,会直接导致 DOMException 或绘图偏移。
为什么 restore() 会抛 DOMException: The operation is not supported
这个错误本质是状态栈为空时调用了 restore()。不是语法错,而是运行时栈管理失败。
-
save()调用次数少于restore()—— 比如循环中无条件执行三次restore(),但只在循环外调了一次save() - 分支逻辑漏配对:if 块里有
save(),else 块里没补restore()(或反之),路径一跑就失衡 - 异步误用:在
setTimeout(() => ctx.restore(), 100)里调用,但同步代码早已把栈弹空 - 旧版 Safari 在嵌套 >15 层时可能静默丢状态,Chrome/Firefox 的 Canvas Inspector 可查当前栈深度,调试时务必打开
哪些状态会被 save()/restore() 实际捕获
它只管上下文“设置”,不管“结果”。每次 save() 打包的是当前生效的全部可变属性快照:
- 变换矩阵:
translate()、rotate()、scale()、transform()累积后的坐标系(原点、角度、缩放) - 裁剪路径:由
clip()建立的区域(仅路径形状,不包含已绘制内容) - 样式与合成:
fillStyle、strokeStyle、globalAlpha、globalCompositeOperation、shadowColor、shadowOffsetX等 - 文本与线条:
font、textAlign、textBaseline、lineWidth、lineCap、lineJoin
以下**完全不保存**:canvas 尺寸(width/height)、已绘制像素、getImageData() 数据、当前路径(beginPath() 后的 path 对象)、图像位图内容。
模块化绘图时如何安全封装 save/restore
核心原则是「自包含、成对、就近」——每个绘制函数自己管自己的状态,不依赖外部上下文。
- 每个组件函数内部必须成对出现:
ctx.save()→ 绘制 →ctx.restore(),哪怕只改一个fillStyle - 避免包裹单条命令,比如
ctx.save(); ctx.fillRect(0,0,10,10); ctx.restore();—— 纯开销,无实际隔离意义 - 若只需重置坐标系(回到原始原点、无旋转缩放),优先用
ctx.setTransform(1, 0, 0, 1, 0, 0),比restore()更轻量、无栈操作 - 高频动画中慎用多层
save(),每帧多次压栈会增加内存与性能负担;可考虑用离屏 canvas 预渲染静态部分
嵌套调用时的状态还原顺序
它是严格的后进先出(LIFO)栈。例如:
ctx.save(); // 状态 A(初始) ctx.rotate(0.2); ctx.save(); // 状态 B(含旋转) ctx.scale(1.5, 1.5); ctx.save(); // 状态 C(含旋转+缩放) // ... 绘制 ctx.restore(); // 弹出 C → 回到 B ctx.restore(); // 弹出 B → 回到 A ctx.restore(); // 弹出 A → 栈空,再调用会报错
最容易被忽略的是:漏掉某一层 restore() 不会立刻报错,但后续所有绘制都会持续受污染——比如 translate 偏移累积、fillStyle 错乱、文字位置越画越偏,这种“渐进式错乱”比直接报错更难定位。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











