状态保存与恢复

Canvas save/restore方法详解,掌握状态栈管理、嵌套使用和最佳实践。save和restore方法用于管理绑图上下文的状态栈,是实现复杂绑制的关键技术。

基本语法

ctx.save()     // 保存当前状态到状态栈
ctx.restore()  // 从状态栈恢复最近保存的状态

保存的状态内容

save方法会保存以下状态:

状态类型包含属性
变换变换矩阵
裁剪裁剪区域
样式fillStyle, strokeStyle
线条lineWidth, lineCap, lineJoin, miterLimit, lineDash
文本font, textAlign, textBaseline, direction
阴影shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY
平滑imageSmoothingEnabled
合成globalAlpha, globalCompositeOperation

工作原理

Canvas使用状态栈来管理状态:

ctx.save()     // 状态栈: [状态1]
ctx.save()     // 状态栈: [状态1, 状态2]
ctx.save()     // 状态栈: [状态1, 状态2, 状态3]
ctx.restore()  // 状态栈: [状态1, 状态2],恢复状态3
ctx.restore()  // 状态栈: [状态1],恢复状态2

基本使用演示

状态保存与恢复

嵌套使用

save/restore可以嵌套使用,形成状态层级:

ctx.fillStyle = 'blue'

ctx.save()           // 保存: blue
ctx.fillStyle = 'red'

  ctx.save()         // 保存: red
  ctx.fillStyle = 'green'
  // 绑制绿色
  ctx.restore()      // 恢复: red

// 绑制红色
ctx.restore()        // 恢复: blue

// 绑制蓝色

嵌套演示

嵌套状态管理

实际应用场景

1. 绘制多个独立图形

function drawShape(ctx, x, y, color, rotation) {
  ctx.save()
  ctx.translate(x, y)
  ctx.rotate(rotation)
  ctx.fillStyle = color
  ctx.fillRect(-25, -25, 50, 50)
  ctx.restore()
}

drawShape(ctx, 100, 100, 'red', 0)
drawShape(ctx, 200, 100, 'blue', Math.PI / 4)
drawShape(ctx, 300, 100, 'green', Math.PI / 2)

2. 绘制图形组

绘制独立图形

3. 绘制复杂场景

复杂场景绘制

状态栈深度

状态栈有最大深度限制,不同浏览器实现不同:

// 检查状态栈深度
let depth = 0
try {
  while (true) {
    ctx.save()
    depth++
  }
} catch (e) {
  console.log('最大状态栈深度:', depth)
}

不保存的内容

以下内容不会被save保存:

  • 当前路径(beginPath后的路径)
  • 当前绑制内容(已绑制的像素)
  • Canvas尺寸
ctx.beginPath()
ctx.rect(0, 0, 100, 100)
ctx.save()        // 路径不会被保存!

ctx.beginPath()   // 新路径会覆盖旧路径
ctx.rect(50, 50, 100, 100)
ctx.restore()     // 恢复后,路径仍然是新路径

ctx.fill()        // 填充的是新路径

最佳实践

1. 成对使用

// 推荐:save和restore成对使用
ctx.save()
// 变换和绑制
ctx.restore()

// 不推荐:忘记restore
ctx.save()
// 变换和绑制
// 忘记restore,可能导致状态栈溢出

2. 函数封装

function withTransform(ctx, transform, draw) {
  ctx.save()
  transform(ctx)
  draw(ctx)
  ctx.restore()
}

// 使用
withTransform(ctx, ctx => {
  ctx.translate(100, 100)
  ctx.rotate(Math.PI / 4)
}, ctx => {
  ctx.fillRect(-25, -25, 50, 50)
})

3. 避免过度嵌套

// 不推荐:过度嵌套
ctx.save()
  ctx.save()
    ctx.save()
      ctx.save()
        // 绑制
      ctx.restore()
    ctx.restore()
  ctx.restore()
ctx.restore()

// 推荐:扁平化
ctx.save()
// 绑制1
ctx.restore()

ctx.save()
// 绑制2
ctx.restore()

注意事项

路径不保存

save不会保存当前路径,需要单独管理。

栈溢出

过度嵌套可能导致状态栈溢出。

性能考虑

save/restore有一定性能开销,大量绑制时考虑使用setTransform。

成对使用

确保save和restore成对使用,避免状态不一致。