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
// 绑制蓝色
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)
状态栈有最大深度限制,不同浏览器实现不同:
// 检查状态栈深度
let depth = 0
try {
while (true) {
ctx.save()
depth++
}
} catch (e) {
console.log('最大状态栈深度:', depth)
}
以下内容不会被save保存:
ctx.beginPath()
ctx.rect(0, 0, 100, 100)
ctx.save() // 路径不会被保存!
ctx.beginPath() // 新路径会覆盖旧路径
ctx.rect(50, 50, 100, 100)
ctx.restore() // 恢复后,路径仍然是新路径
ctx.fill() // 填充的是新路径
// 推荐:save和restore成对使用
ctx.save()
// 变换和绑制
ctx.restore()
// 不推荐:忘记restore
ctx.save()
// 变换和绑制
// 忘记restore,可能导致状态栈溢出
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)
})
// 不推荐:过度嵌套
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成对使用,避免状态不一致。