绘制状态存储

1. 前言

canvas 中设置属性多数都是全局使用的,我们在操作时难免会遇到某个属性被修改后,当再次使用到这个属性时必须是默认值的情况,或者我们绘制多个矩形,这些矩形的填充颜色和阴影有一定规律,比如填充颜色为 红、黄、蓝、黄、红这样的顺序,阴影也是有对应顺序,我们应该怎么做呢?本小节我们就来学习如何保存某个绘制状态和快速地将绘制状态恢复到上一个阶段。

2. 存储绘制状态

我们先看一个不使用存储绘制状态的案例,这个案例中,我们需要绘制5个矩形,其中第一个和第五个矩形相同,第二个和第四个矩形相同。

实例演示
预览 复制
复制成功!
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>慕课网Wiki</title>
    <style>
        #imooc{
            border:1px solid #ccc;
        }
    </style>
</head>
<body>
    <canvas id="imooc">您的浏览器不支持 HTML5 canvas 标签</canvas>
    <script>
		const canvas = document.getElementById('imooc');
		canvas.width=360;
		canvas.height=130;
		const ctx = canvas.getContext('2d');
		
		// 绘制第一个矩形
		ctx.fillStyle="red"
		ctx.shadowBlur=2;
		ctx.shadowOffsetX=4;
		ctx.shadowOffsetY=4;
		ctx.shadowColor="#ccc"
		ctx.fillRect(40,40, 40,40)
		
		// 绘制第二个矩形
		ctx.fillStyle="yellow"
		ctx.shadowBlur=3;
		ctx.shadowOffsetX=8;
		ctx.shadowOffsetY=8;
		ctx.shadowColor="#456795"
		ctx.fillRect(100,40, 40,40)
		
		
		// 绘制第三个矩形
		ctx.fillStyle="blue"
		ctx.shadowBlur=5;
		ctx.shadowOffsetX=5;
		ctx.shadowOffsetY=5;
		ctx.shadowColor="#222"
		ctx.fillRect(160,40, 40,40)
		
		// 绘制第四个矩形
		ctx.fillStyle="yellow"
		ctx.shadowBlur=3;
		ctx.shadowOffsetX=8;
		ctx.shadowOffsetY=8;
		ctx.shadowColor="#456795"
		ctx.fillRect(220,40, 40,40)
		
		// 绘制第五个矩形
		ctx.fillStyle="red"
		ctx.shadowBlur=2;
		ctx.shadowOffsetX=4;
		ctx.shadowOffsetY=4;
		ctx.shadowColor="#ccc"
		ctx.fillRect(280,40, 40,40)
		
	</script>
<body>
</html>
运行案例 点击 "运行案例" 可查看在线运行效果

运行结果:

上面案例中,可以看到,绘制第四个正方形的时候,把所有属性又写了一遍,但是这个属性和我们绘制第二个正方形的属性一样,第五个正方形和第一个正方形的属性一样,这样我们不仅浪费时间还增加了代码维护成本,维护成本主要指:假如我们要修改红色的正方形为其他颜色,我们就得修改两处代码。

今天我们就用存储绘制状态的方法来优化一下上面代码,还是上面那个案例,我们换一种写法。

实例演示
预览 复制
复制成功!
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>慕课网Wiki</title>
    <style>
        #imooc{
            border:1px solid #ccc;
        }
    </style>
</head>
<body>
    <canvas id="imooc">您的浏览器不支持 HTML5 canvas 标签</canvas>
    <script>
		const canvas = document.getElementById('imooc');
		canvas.width=360;
		canvas.height=130;
		const ctx = canvas.getContext('2d');
		
		// 绘制第一个矩形
		ctx.fillStyle="red"
		ctx.shadowBlur=2;
		ctx.shadowOffsetX=4;
		ctx.shadowOffsetY=4;
		ctx.shadowColor="#ccc"
		ctx.save();    // 这里把当前画布的属性做了一个标记,我们称为:标记一
		ctx.fillRect(40,40, 40,40)
		
		// 绘制第二个矩形
		ctx.fillStyle="yellow"
		ctx.shadowBlur=3;
		ctx.shadowOffsetX=8;
		ctx.shadowOffsetY=8;
		ctx.shadowColor="#456795"
		ctx.save();    // 这里把当前画布的属性做了第二个标记,我们称为:标记二
		ctx.fillRect(100,40, 40,40)
		
		// 绘制第三个矩形
		ctx.fillStyle="blue"
		ctx.shadowBlur=5;
		ctx.shadowOffsetX=5;
		ctx.shadowOffsetY=5;
		ctx.shadowColor="#222"
		ctx.fillRect(160,40, 40,40)
		
		// 绘制第四个矩形
		ctx.restore()  // 这里我们读取了最新的一个标记,也就是读区了标记二的状态,标记二被读取后就消失了。
		ctx.fillRect(220,40, 40,40)
		
		// 绘制第五个矩形
		ctx.restore(); // 这里我们读取了最新的一个标记,也就是读区了标记一的状态,因为标记二已经消失了
		ctx.fillRect(280,40, 40,40)
		
	</script>
<body>
</html>
运行案例 点击 "运行案例" 可查看在线运行效果

运行结果:

我们可以看到,运行结果是一样的,我们把主要代码拆分讲解一下。

  1. 设置绘制矩形的相关属性,并调用 save 方法保存一个绘制状态。

    	ctx.fillStyle="red"
    	ctx.shadowBlur=2;
    	ctx.shadowOffsetX=4;
    	ctx.shadowOffsetY=4;
    	ctx.shadowColor="#ccc"
    	ctx.save();    // 这里把当前画布的属性做了一个标记,我们称为:标记一
    	ctx.fillRect(40,40, 40,40)
    
    
  2. 设置第二个矩形的相关属性,并调用 save 方法保存一个绘制状态,这个状态会堆放到上一个状态的上面,该状态的存储符合“栈”的特性:先进后出,也就是最先放进去的最后才被拿走。

    我们看一个形象的图片,小孩的玩具。

    最下面的大圈是最先放进去的,再取出时它是最后一个被拿出来的。

    	ctx.fillStyle="yellow"
    	ctx.shadowBlur=3;
    	ctx.shadowOffsetX=8;
    	ctx.shadowOffsetY=8;
    	ctx.shadowColor="#456795"
    	ctx.save();    // 这里把当前画布的属性做了第二个标记,我们称为:标记二
    	ctx.fillRect(100,40, 40,40)
    
    
  3. 绘制第四个矩形,我们在绘制前先调用 ctx.restore 方法取出一个状态,取出的状态会应用到当前画布上。当前存储的所有状态中,最上面的是标签二,也就是第二个矩形的状态,所以这里取出来的就是第二个矩形的状态属性。

    	ctx.restore()  // 取出状态
    	ctx.fillRect(220,40, 40,40)
    
  4. 绘制第五个矩形,同样调用 ctx.restore 方法取出一个状态,当前存储的状态中只有标签一,也就是第一个矩形的状态,所以这里取出来的就是第一个矩形的状态属性。

    	ctx.restore(); // 取出状态
    	ctx.fillRect(280,40, 40,40)
    

特别注意 当存储的状态被取完以后,再去取一个状态,此时会出问题。解决办法就是:读取状态 restore 的次数只能小于等于存储 save 的次数。

3. 方法整理

本小节我们学习了 save 方法和 restore 方法,它们的主要作用是存储某一个阶段的属性状态,后面可以快速恢复存储的状态。

3.1 存储状态 save

save 说明

  • save 方法主要目的是存储 canvas 当前全部状态,此方法可以多次执行,每次执行都会存储一次当前状态。

语法:

ctx.save();

变量说明:

没有参数。

3.2 存储状态 restore

restore 说明

  • restore 方法主要是用于取出存储在 canvas 中的最新状态,此方法可以多次执行,但是不能超过已存储状态的次数。

语法:

ctx.restore();

变量说明:

没有参数。

4. 总结

本小节我们主要学习了利用 saverestore 存储和拿取画布状态的方法,存储状态的目的是后面可以快速恢复到上一个绘制状态。本小节我们只是简单体会了存储和拿取的过程,后面小节我们学习了旋转变形的操作,会频繁地用到这个操作。