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)。

恢复多个绘图状态

关于绘图状态的保存和恢复还有很多其他内容,本节的目的只是介绍一些基础知识。从现在开始,你就能够理解后续章节关于绘图状态的使用方法了,因此能够有更充裕的时间学习其他更有趣的功能。