4.7 使用栈处理多重变换
通过HTML5的画布API,我们可以很好的处理变换,现在我们将进一步探讨画布的状态栈,看看它对变换的作用。在第2章 图形及组合中,我们曾经探讨过状态栈,它是画布API中一个非常强大,而往往被忽视的特性。虽然状态栈可以帮助管理样式,但是最常用的,还是保存和恢复变换状态。本节,我们将进行多重变换,在每重变换过程中,都会保存画布状态,然后在恢复每个状态后,再绘制一系列矩形,看看其效果。
绘制步骤
按照以下步骤,构造一个有四个不同状态的状态栈,然后,在弹出每个状态之后,绘制矩形:
1. 定义画布上下文,及矩形的尺寸:
window.onload = function(){
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
var rectWidth = 150;
var rectHeight = 75;
2. 把当前变换状态,即默认状态,压入栈顶,并平移上下文:
context.save(); //保存状态1
context.translate(canvas.width / 2, canvas.height / 2);
3. 把当前变换状态,即平移状态,压入栈顶,并旋转上下文:
context.save(); //保存状态2
context.rotate(Math.PI / 4);
4. 把当前变换状态,即平移状态和旋转状态,压入栈顶,并缩放上下文:
context.save(); //保存状态3
context.scale(2, 2);
5. 绘制该矩形:
// 绘制该矩形
context.fillStyle = "blue";
context.fillRect(-rectWidth / 2, -rectHeight / 2, rectWidth, rectHeight);
6. 通过弹出当前状态,来从状态栈恢复出前一个状态,并绘制一个红色矩形:
context.restore(); // 恢复状态3
context.fillStyle = "red";
context.fillRect(-rectWidth / 2, -rectHeight / 2, rectWidth, rectHeight);
7. 通过弹出当前状态,来从状态栈中恢复出前一个状态,并绘制一个黄色矩形:
context.restore(); //恢复状态2
context.fillStyle = "yellow";
context.fillRect(-rectWidth / 2, -rectHeight / 2, rectWidth, rectHeight);
8. 通过弹出当前状态,来从状态栈中恢复出前一个状态,并绘制一个绿色矩形:
context.restore(); //恢复状态1
context.fillStyle = "green";
context.fillRect(-rectWidth / 2, -rectHeight / 2, rectWidth, rectHeight);
};
9. 在HTML文档的body部分嵌入canvas标签:
<canvas id="myCanvas" width="600" height="250" style="border:1px solid black;">
</canvas>
工作原理
本节,我们进行了三次变换,一次平移、一次旋转、一次缩放,在每次变换时,都执行save()操作把变换状态压栈。当蓝色矩形绘制完成,它是被居中的、被旋转的、并被缩放的。此时,状态栈中有四个状态(从栈底到栈顶):
1. 默认状态
2. 平移状态
3. 平移和旋转状态
4. 当前状态(平移、旋转、缩放状态)
蓝色矩形绘制完成后,我们调用restore()方法来弹出栈顶的状态,把画布状态恢复到第三个状态,此时画布上下文是被平移和旋转的状态。红色矩形绘制完成后,你可以看到它是被平移和旋转的,但没有被缩放。其次,我们再一次调用restore()方法来弹出栈顶的状态,把画布状态恢复到第二个状态,此时画布上下文只有平移状态。我们再绘制黄色矩形,它的确仅仅被平移。最后,我们最后一次调用restore()方法来弹出栈顶的状态,并回到默认状态。当我们绘制绿色矩形,它出现在初始位置,因为没有对它应用任何变换。
使用状态栈,我们可以在变换状态之间进行跳转,从而不必不断的把恢复状态栈到默认状态,再分别平移每个元素。另外,我们也可以使用保存-恢复组合把一小段坐标变换的代码包裹起来,使其不影响后面绘制的图形。