canvas中save和restore的理解和使用
理解save和restore不一定总是成对出现的
每个
canvas
的context
上下文都包含一个保存绘画状态的栈,以下内容都属于绘画状态
- 当前的变换矩阵
- 当前的裁剪区域
strokeStyle
、fillStyle
、lineWidth
、lineCap
、font
、textAlign
、shadowOffsetX
… 这些属性的当前值当前路径和当前的位图并不属于绘画状态,当前路径是永久存在的,要清除或者重置只能通过 beginPath 方法,而当前位图是属于
canvas
的属性,而不是context
的
context.save()
会把当前的状态推入栈中
context.restore()
会从栈中取出最顶部的状态,context
就还原到取出的状态
<canvas id="canvas" width="500" height="300"></canvas>
<script>
window.addEventListener('load', saveRestore, true)
function saveRestore() {
let canvas = document.getElementById('canvas')
let context = canvas.getContext('2d')
// 绘制了一个红色的矩形
context.fillStyle = 'red'
context.shadowOffsetX = 5
context.shadowOffsetY = 5
context.shadowBlur = 4
context.shadowColor = 'rgba(0, 255, 0, 0.3)'
context.fillRect(20, 20, 50, 150)
context.save() // 当前状态1保存(红色,绿色阴影5)推入栈中
// 绘制一个绿色的矩形
context.fillStyle = 'green'
context.shadowOffsetX = 10
context.shadowOffsetY = 10
context.shadowBlur = 4
context.shadowColor = 'rgba(255, 0, 0, 0.3)'
context.fillRect(100, 40, 50, 150)
context.save() // 当前状态2保存(绿色,红色阴影10)推入栈中
// 绘制一个橘色的矩形
context.fillStyle = 'orange'
context.shadowOffsetX = 15
context.shadowOffsetY = 15
context.shadowBlur = 4
context.shadowColor = 'rgba(0, 0, 255, 0.3)'
context.fillRect(190, 60, 50, 150)
context.save() // 当前状态3保存(橘色,蓝色阴影15)推入栈中
}
</script>
此时绘制的状态为
现在通过从栈中取出不同的状态绘制圆
...
context.save() // 当前状态3保存(橘色,蓝色阴影15)推入栈中
context.restore() // 当前取出的是最顶上的状态(状态3)
// 根据以上取出栈中的样式绘制圆
context.beginPath()
context.arc(300, 75, 15, 0, Math.PI * 2, true)
context.fill()
...
context.save() // 当前状态3保存(橘色,蓝色阴影15)推入栈中
context.restore() // 当前取出的是最顶上的状态(状态3)
context.restore() // 当前取出的是最顶上的状态(状态2)
// 根据以上取出栈中的样式绘制圆
context.beginPath()
context.arc(300, 75, 15, 0, Math.PI * 2, true)
context.fill()
context.save() // 当前状态3保存(橘色,蓝色阴影15)推入栈中
context.restore() // 当前取出的是最顶上的状态(状态3)
context.restore() // 当前取出的是最顶上的状态(状态2)
context.restore() // 当前取出的是最顶上的状态(状态1)
// 根据以上取出栈中的样式绘制圆
context.beginPath()
context.arc(300, 75, 15, 0, Math.PI * 2, true)
context.fill()
太阳系
下面看一个小案例好好理解和消化一下吧
const WIDTH = 800, SPAN = 40
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
canvas.width = WIDTH
canvas.height = WIDTH
canvas.style.background = '#000'
// 轨道
function drawTrack() {
for(let i=0; i < 8; i++){
ctx.beginPath()
ctx.arc(WIDTH / 2, WIDTH / 2, (i + 1) * SPAN, 0, 360, false)
ctx.closePath()
ctx.strokeStyle = '#fff'
ctx.stroke()
}
}
drawTrack()
// 星球
class Star {
constructor(x, y, radius, cycle, startColor, endColor) {
// 星球坐标
this.x = x
this.y = y
this.radius = radius // 星球半径
this.cycle = cycle // 公转周期
this.color = null // 新建一个渐变颜色空对象
this.time = 0
this.startColor = startColor
this.endColor = endColor
}
draw() {
ctx.save()
ctx.translate(WIDTH / 2, WIDTH / 2)
// rotate 针对的是绘制的图形,以translate的坐标为圆点进行旋转
ctx.rotate(this.time * (360 / this.cycle) * Math.PI / 180)
// 画星球
ctx.beginPath()
ctx.arc(this.x, this.y, this.radius, 0, 360, false)
ctx.closePath()
// 设置星球的渐变填充颜色
this.color = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.radius)
this.color.addColorStop(0, this.startColor)
this.color.addColorStop(1, this.endColor)
ctx.fillStyle = this.color
ctx.fill()
ctx.restore()
this.time += 1
}
}
const sun = new Star(0, 0, 10, 1, '#f00', '#f90')
const mercury = new Star(0, -SPAN, 10, 87.70, '#a69697', '#5c3e40')
const venus = new Star(0, -SPAN * 2, 10, 224.70, '#c4bbac','#1f1315')
const earth = new Star(0, -SPAN * 3, 10, 365.2422, '#78b1e8', '#050c12')
const mars = new Star(0, -SPAN * 4, 10, 686.98, '#cec9b6', '#76422d')
const jupiter = new Star(0, -SPAN * 5, 10, 4332.589, '#cda48e', '#322222')
const satum = new Star(0, -SPAN * 6, 10, 10759.5, '#f7f9e3', '#5c4533')
const uranus = new Star(0, -SPAN * 7, 10, 30799.095, '#f7f9e3', '#19243a')
const neptune = new Star(0, -SPAN * 8, 10, 60152, '#0661b2', '#1e3b73')
function move(){
ctx.clearRect(0, 0, 1000, 1000)
drawTrack()
sun.draw()
mercury.draw()
venus.draw()
earth.draw()
mars.draw()
jupiter.draw()
satum.draw()
uranus.draw()
neptune.draw()
}
setInterval(move, 10)