21

此页面显示了 HTML5 画布中的一些动画。如果您查看滚动条的来源,则有一条语句可以在清除矩形后保存上下文并在动画后恢复它。如果我用另一个语句替换还原语句ctx.clearRect(0, 0, can.width, can.height,则没有任何效果。我认为恢复正在恢复清除的矩形,但它似乎恢复了更多信息。下一帧需要哪些额外信息?

我不是在寻找保存和恢复的 HTML5 教科书定义,但我想了解为什么在这个特定示例中需要它们。

更新

在我不想得到 save() 和 restore() 的定义的问题中特别提到的地方得到一个答案是令人沮丧的。我已经知道 Save() 保存上下文的状态, Restor()e 恢复它。我的问题非常具体。当所有 Save 所做的一切都保存在一个空的画布上时,为什么以示例中的方式使用 restore()。为什么恢复空画布清除它不同?

4

2 回答 2

43

画布状态不是画在上面的。它是一组属性,定义了用于绘制下一事物的工具的当前状态。

Canvas 是立即模式位图。就像 MS 油漆。一旦它在那里,它就在那里,所以没有必要“保存”当前的图像数据,因为这就像保存整个 JPEG,每次你做出改变,每一帧......

...不,您保存的状态将决定您用来绘制 NEXT 事物的坐标方向、尺寸比例、颜色等(以及之后的所有事物,直到您手动更改这些值)。

var canvas = document.createElement("canvas"),
    easel  = canvas.getContext("2d");

easel.fillStyle = "rgb(80, 80, 120)";
easel.strokeStyle = "rgb(120, 120, 200)";

easel.fillRect(x, y, width, height);
easel.strokeRect(x, y, width, height);

easel.save();  // stores ALL current status properties in the stack

easel.rotate(degrees * Math.PI / 180); // radians
easel.scale(scale_X, scale_Y); // any new coordinates/dimensions will now be multiplied by these
easel.translate(new_X, new_Y); // new origin coordinates, based on rotated orientation, multiplied by the scale-factor

easel.fillStyle = "gold";
easel.fillRect(x, y, width, height); // completely new rectangle
// origin is different, and the rotation is different, because you're in a new coordinate space

easel.clearRect(0, 0, width, height); // not even guaranteed to clear the actual canvas, anymore
easel.strokeRect(width/2, height/2, width, height); // still in the new coordinate space, still with the new colour


easel.restore(); // reassign all of the previous status properties
easel.clearRect(0, 0, width, height);

假设您只是堆栈深处的一个状态更改,那么最后一行,现在您的画布的先前状态已恢复,应该已成功清除自身(尽管有亚像素恶作剧)。

如您所见,它与擦除画布几乎没有关系。
事实上,它根本与擦除它无关。

它与想要画一些东西有关,做基本的轮廓和清扫颜色/样式,然后在顶部手动写下较小细节的颜色,然后手动将所有样式写回原来的样子,回到下一个对象的全面笔触,然后继续......

相反,保存将被重用的一般状态,为较小的细节创建一个新状态,然后返回到一般状态,而不必每次都对其进行硬编码,或者编写 setter 函数来设置画布上的常用值及以上(重置比例/旋转/仿射变换/颜色/字体/线宽/基线对齐/等)。

那么,在您的确切示例中,如果您注意,您会发现唯一变化的是 的值step

他们为画布设置了一堆值的状态(颜色/字体/等)。
然后他们保存。那么,他们保存了什么?
你看的不够深。他们实际上保存了默认翻译(即:原始世界空间中的 origin=0,0)
但你没有看到他们定义它?
那是因为它被定义为默认值。

然后他们增加第 1 步像素(实际上,他们首先这样做,但在第一个循环之后并不重要 - 留在我这里)。
然后他们为 0,0 设置了一个新的原点(即:从现在开始,当他们键入时0,0,新的原点将指向画布上完全不同的位置)。

该原点等于 x 是画布的确切中间,而 y 等于当前步骤(即:像素 1 或像素 2 等...以及为什么从 0 开始和从 1 开始之间的差异确实没有没关系)。

那么他们做什么呢?
他们恢复。

那么,他们恢复了什么?
……嗯,他们有什么变化?

他们正在将原点恢复到 0,0
为什么?

好吧,如果他们不这样做会发生什么?
如果画布是 500 像素 x 200 像素,并且在我们当前的屏幕空间中从 0,0 开始……那太好了……
然后他们将其转换为 width/2, 1
好的,所以现在当他们要求绘制时0,0 处的文字 他们实际上会在 250, 1 处绘制

精彩的。但是下次会发生什么呢?

现在他们正在按 width/2, 2 平移
你认为,嗯,这很好...... ... 0,0 的绘制调用将发生在 250, 2,因为他们已将其设置为清除数字: canvas.width/2, 2

没有。因为根据我们的屏幕,当前 0,0 实际上是 250,1。一个翻译是相对于它之前的翻译...

...所以现在您要告诉画布从当前坐标的 0,0 开始,然后向左 250,然后向下 2。
根据屏幕(就像一个窗口,看着地图,而不是地图本身)我们现在在右边 500 像素处,从我们开始的地方向下 3 像素......而且只有一帧过去了。

因此,在设置新的坐标之前,他们将地图的坐标恢复为与屏幕坐标相同的原点(并且旋转相同,比例,倾斜等......)。

正如您可能猜到的那样,现在通过查看它,您可以看到文本实际上应该从上到下移动。不是从右到左,就像页面上说的那样......

为什么要这样做?
为什么要麻烦改变绘图上下文的坐标系,当绘图命令在函数中给你一个xandy时?

如果你想在画布上画一幅画,并且你知道它有多高和多宽,以及你希望左上角在哪里,你为什么不能这样做:

easel.drawImage(myImg, x, y, myImg.width, myImg.height);

嗯,你可以。
你完全可以做到这一点。没有什么能阻止你。

事实上,如果你想让它在屏幕上缩放,你可以在计时器上更新xand ,然后就结束了。y

但是如果你在画一个游戏角色呢?如果这个角色有一顶帽子,戴着手套的手,还有一双大靴子,而所有这些东西都与角色分开了呢?

所以首先你会说“好吧,他站在世界上的 x 和 y,所以 x 加上他的手相对于他的身体的位置将是 x + body.x - hand.x ......或者是那个加号。 ..”

......现在你可以为他所有的部分绘制调用,这些部分看起来都像一本装满五年级数学作业的笔记本。

相反,您可以说:“他在这里。设置我的坐标,使 0,0 正好在我的人中间”。现在您的绘图调用就像“我的右手在身体右侧 6 像素,我的左手在左侧 3 像素”一样简单。

当你画完你的角色后,你可以将你的原点设置回 0,0,然后可以绘制下一个角色。或者,如果你想尝试它,你可以从那里翻译到下一个字符的原点,基于从一个到另一个的增量(这将为你节省每次翻译的函数调用)。然后,如果你一直只保存一次状态(原始状态),最后,你可以通过调用返回 0,0 .restore

于 2013-05-08T02:04:12.617 回答
1

上下文 save() 保存转换颜色等内容。然后您可以更改上下文并将其恢复为与保存时相同。它像一个堆栈一样工作,因此您可以将多个画布状态推送到堆栈上并恢复它们。 http://html5.litten.com/understanding-save-and-restore-for-the-canvas-context/

于 2013-05-06T23:58:39.410 回答