canvas.save()必须配对restore(),否则导致内存泄漏、绘图错位或消失;仅保存变换矩阵、裁剪路径、globalAlpha等状态,fillStyle等需手动备份;高频动画中应避免滥用,优先用setTransform或离屏canvas优化性能。
canvas.save() 之后必须配对使用 canvas.restore()
不配对会导致图形栈持续增长,内存泄漏,后续绘图错位或消失。常见现象是:画布突然不显示新内容、translate 或 rotate 效果“累积生效”、文字位置越来越偏。
实际场景中,比如在绘制多个带旋转的图标时,每个图标前调用 save(),画完立刻 restore()——不是等所有图标画完再统一恢复。
- 每次
save()把当前变换矩阵、裁剪区域、全局透明度等压入栈;restore()弹出最近一次保存的状态 - 嵌套调用合法,但深度不宜超过 10 层(部分旧版 Safari 在 >15 层时开始丢状态)
- 没调用
save()就restore()不报错,但会静默失败(栈空时无操作),容易误以为“恢复成功”
哪些状态会被 save()/restore() 捕获
不是所有 canvas 设置都进栈。只包括:当前变换矩阵(setTransform、translate 等)、裁剪路径(clip())、全局 alpha(globalAlpha)、合成操作(globalCompositeOperation)、阴影设置(shadowColor 等)。
不会保存的有:fillStyle、strokeStyle、lineWidth、字体相关属性(font、textAlign)——这些得手动备份/重设。
- 想临时改颜色又还原?自己存
const oldFill = ctx.fillStyle,画完再赋值回去 -
save()后修改了lineWidth,restore()不会变回来,别指望它 - 裁剪路径(
clip())会被保存,但裁剪本身不可逆——restore()只恢复“是否启用裁剪”和“裁剪区域形状”,不是撤销裁剪操作
用 save() 做局部动画时的性能陷阱
高频重绘(如 60fps 动画)中滥用 save()/restore() 会明显拖慢。Chrome DevTools 的 Performance 面板里常看到 CanvasRenderingContext2D.restore 占用高 CPU 时间。
立即学习“前端免费学习笔记(深入)”;
典型错误:每帧对整个画布 save(),再逐个元素 restore()。其实多数时候只需保存局部变换,而非全量状态。
- 动画中只动位置?用
setTransform(1,0,0,1,x,y)替代translate(x,y)+save(),避免栈操作 - 真需要隔离状态?优先用离屏 canvas(
document.createElement('canvas'))预渲染,再drawImage()上屏,比反复 save/restore 快 3–5 倍 - 移动端尤其敏感:iOS Safari 对图形栈操作更重,连续 3 层以上嵌套 save/restore 容易掉帧
调试时怎么确认 save()/restore() 是否漏配对
没有内置 API 能查当前栈深,但可以加轻量级检测逻辑。别依赖 console 打印,要能定位到具体哪一行漏了。
- 开发环境给 ctx 加代理:拦截
save和restore,用计数器跟踪,restore()时若计数 ≤ 0 就抛new Error('restore without save at ' + new Error().stack) - 用
getTransform()辅助判断:如果连续两次调用返回矩阵完全一样,大概率前面的save()没被restore()消费 - 注意:某些库(如 fabric.js)内部已大量使用
save/restore,自行封装时需预留兼容层,别假设初始栈深为 0
图形栈不是黑盒,它很老实——你压多少,它就存多少;你弹几次,它就吐几次。漏一次 restore(),后面所有变换都会偏移,而且这种偏移往往隔几帧才显现,排查起来最磨人。











