12.8 HTML 画布绘图状态
前面的例子中我们经常在各种样式之间切换,甚至有时候会在不同颜色之间反复切换。这种重复是很麻烦的,它意味着如果你想要返回之前使用的一些样式,必须重写大量的代码。 幸好,画布能够记住一些样式和属性,这样将来你就可以再次使用。这就是所谓的保存和恢复画布绘图状态。然而,问题是,如果要记住多个状态,操作起来可能令人困惑,因为你必须跟踪所有发生的变化。
画布绘图状态是什么
无论是在现实世界还是画布中,“状态”这个词都是用来描迷事物在特定时刻所处的状况。重要的是要抓住与所描述时间直接关联的对象状态。
例如,如果我要描述你昨天及今天的状态,那么它们一定是两个完全不同的状态。简而言之,状态总在变化。
在画布中,绘图状态指的是描述某一时刻2D渲染上下文外观的整套属性,从简单的颜色值到复杂的变换矩阵(transformation matrix)及其他特性。
用于描述画布绘图状态的全部属性为:变换矩阵、裁剪区域(clipping region)、globalAlpha、globalCompositeOperation、strokeStyle、fillStyle、lineWidth、lineCap、lineJoin、miterLimit、shadowOffsetX、shadowOffsetY、shadowBlur、shadowColor、font、textAlign和textBaseline。
有一点很重要,画布上的当前路径和当前位图(正在显示的内容)并不属于状态。我们更应该将状态看做2D渲染上下文属性的描述,而不是画布上显示的所有内容的副本。
保存绘图状态
保存画布状态非常简单。你需要做的就是调用2D渲染上下文的save
方法。仅此而已。save方法的代码如下:
var canvas = $("#myCanvas"); var context = canvas.get(0).getContext("2d"); context.fillStyle = "rgb(255, 0, 0)"; context.save(); // Save the canvas state context.fillRect(50, 50, 100, 100); // Red square
那么,当你保存绘图状态时,实际上发生了什么呢?可以肯定的是,它必须保存在某个地方。2D渲染上下文会保存一个绘图状态栈,实际上它是一组之前保存的状态,其中最近保存的状态位于顶部——就像一叠纸。绘图状态的默认栈是空的,调用save方法,就会有一个新状态被放入(添加到)这个栈。这意味着,你完全可以多次调用save方法,将多个绘图状态逐一保存到栈中,其中最早的状态在底部。然而,这其中有一点不易理解,那就是你无法将任何绘图状态后移,因为这个过程是有严格顺序的。我们先来了解一下如何访问刚刚保存的状态。
恢复绘图状态
访问一个已有绘图状态与保存它一样简单,唯一的区别是这次调用的是restore
。现在,如果你绘制另一个正方形,并且这次将fillStyle设置为蓝色,那么很快会看到画布绘图状态的好处:
context.fillStyle = "rgb(0, 0, 255)"; context.fillRect(200, 50, 100, 100); // Blue square
这里并没有执行任何特殊操作,唯一修改的是填充颜色。
但是,如果你想换回之前使用的红色填充颜色,该怎么做呢?你无需再次重写fillStyle属性并将它设置为红色,因为前面你将颜色设置为红色之后保存了绘图状态,所以它已经存在于栈中了,你只需要在现有代码之前调用restore,就可以恢复原先的状态:
context.restore(); // Restore the canvas state context.fillRect(350, 50, 100, 100); // Red square
通过调用restore方法,你能够自动取出最后添加到栈中的绘图状态,并将它应用于2D渲染上下文,用所保存的状态覆盖全部现有的样式。这意味着,虽然你没有在代码中直接修改fillStyle属性,但是它将取得所保存的绘图状态的值——它会变成红色(参见图1)。如果只是修改颜色,效果可能还不够明显,但这个概念适用于所有能够保存到绘图状态中的画布属性。
保存和恢复多个绘图状态
在本节开头,我曾提到过一次处理多个状态有一些复杂。但是,在学完前面的内容之后,我希望现在你已经理解该如何处理它了。实话说,如果理解了栈的概念,并且明白新增的项被添加到栈的顶部,并且它们是从栈顶部取回的,那么你就不会觉得它复杂了。栈实际上采用一种后进先出的机制,最近保存到栈的绘图状态将是后来第一个恢复的状态。
如果你修改前面的例子,在将fillStyle设置为蓝色后保存绘图状态,就会明白我的意思:
context.fillStyle = "rgb(255, 0, 0)"; context.save(); context.fillRect(50, 50, 100, 100); // Red square context.fillStyle = "rgb(0, 0, 255)"; context.save(); context.fillRect(200, 50, 100, 100); // Blue square context.restore(); context.fillRect(350, 50, 100, 100); // Blue square
第三个正方形现在不是红色,而是蓝色。这是因为最后保存到栈的绘图状态是蓝色的fillStyle,所以它最先恢复(参见图2)。
另一个状态是红色的fillStyle.它仍然在栈中等待,你只需要再调用一次restore就能够恢复这个状态:
context.restore(); context.fillRect(50, 200, 100, 100); // Red square
这会从栈返回最后一个状态,并将它删除,使栈变成空的(参见图3)。
关于绘图状态的保存和恢复还有很多其他内容,本节的目的只是介绍一些基础知识。从现在开始,你就能够理解后续章节关于绘图状态的使用方法了,因此能够有更充裕的时间学习其他更有趣的功能。
$(document).ready(function () { var canvas1 = $("#canvas1"); var context1 = canvas1.get(0).getContext("2d"); context1.fillStyle = "rgb(255, 0, 0)"; context1.save(); // Save the canvas state context1.fillRect(50, 50, 100, 100); // Red square context1.fillStyle = "rgb(0, 0, 255)"; context1.fillRect(200, 50, 100, 100); // Blue square context1.restore(); // Restore the canvas state context1.fillRect(350, 50, 100, 100); // Red square var canvas2 = $("#canvas2"); var context2 = canvas2.get(0).getContext("2d"); var text = "Hello, World!"; context2.fillStyle = "rgb(255, 0, 0)"; context2.save(); context2.fillRect(50, 50, 100, 100); // Red square context2.fillStyle = "rgb(0, 0, 255)"; context2.save(); context2.fillRect(200, 50, 100, 100); // Blue square context2.restore(); context2.fillRect(350, 50, 100, 100); // Blue square var canvas3 = $("#canvas3"); var context3 = canvas3.get(0).getContext("2d"); context3.fillStyle = "rgb(255, 0, 0)"; context3.save(); // Save the canvas state context3.fillRect(50, 50, 100, 100); // Red square context3.fillStyle = "rgb(0, 0, 255)"; context3.save(); context3.fillRect(200, 50, 100, 100); // Blue square context3.restore(); // Restore the canvas state context3.fillRect(350, 50, 100, 100); // Blue square context3.restore(); // Restore the canvas state context3.fillRect(50, 200, 100, 100); // Red square });