变换
在本教程的前面,我们学习了有关 canvas 网格 和 **坐标空间** 的知识。到目前为止,我们只使用了默认网格,并根据需要调整整个 canvas 的大小。使用变换,我们可以更强大地将原点移动到不同的位置、旋转网格甚至缩放网格。
保存和恢复状态
在我们查看变换方法之前,让我们先看看另外两种方法,一旦你开始生成越来越复杂的图形,它们就必不可少。
Canvas 状态存储在堆栈中。每次调用 save()
方法时,当前绘图状态都会被推入堆栈。绘图状态包含:
- 已应用的变换(即
translate
、rotate
和scale
- 参见下文)。 - 以下属性的当前值:
- 当前的 剪切路径,我们将在下一节中看到。
你可以根据需要多次调用 save()
方法。每次调用 restore()
方法时,最后一个保存的状态都会从堆栈中弹出,并且所有保存的设置都会被恢复。
一个 save
和 restore
canvas 状态的例子
function draw() {
const ctx = document.getElementById("canvas").getContext("2d");
ctx.fillRect(0, 0, 150, 150); // Draw a Black rectangle with default settings
ctx.save(); // Save the original default state
ctx.fillStyle = "#09F"; // Make changes to saved settings
ctx.fillRect(15, 15, 120, 120); // Draw a Blue rectangle with new settings
ctx.save(); // Save the current state
ctx.fillStyle = "#FFF"; // Make changes to saved settings
ctx.globalAlpha = 0.5;
ctx.fillRect(30, 30, 90, 90); // Draw a 50%-White rectangle with newest settings
ctx.restore(); // Restore to previous state
ctx.fillRect(45, 45, 60, 60); // Draw a rectangle with restored Blue setting
ctx.restore(); // Restore to original state
ctx.fillRect(60, 60, 30, 30); // Draw a rectangle with restored Black setting
}
第一步是使用默认设置绘制一个大矩形。接下来,我们保存此状态并更改填充颜色。然后我们绘制第二个更小的蓝色矩形并保存状态。再次更改一些绘图设置并绘制第三个半透明的白色矩形。
到目前为止,这与我们在前面几节中所做的非常相似。但是,一旦我们调用第一个 restore()
语句,最上面的绘图状态就会从堆栈中删除,并且设置会被恢复。如果我们没有使用 save()
保存状态,则需要手动更改填充颜色和透明度才能返回到先前状态。对于两个属性来说这很容易,但如果我们有不止两个属性,我们的代码会很快变得很长。
当第二个 restore()
语句被调用时,原始状态(我们在第一次调用 save
之前设置的状态)会被恢复,最后一个矩形再次被绘制为黑色。
平移
我们首先要看的是变换方法中的 translate()
。此方法用于将 canvas 及其原点移动到网格中的不同点。
translate(x, y)
-
将 canvas 及其原点移动到网格上的位置。
x
指示要移动的水平距离,y
指示要垂直移动网格的距离。
在执行任何变换之前保存 canvas 状态是一个好主意。在大多数情况下,调用 restore
方法比手动反向平移以返回到原始状态更容易。此外,如果你在循环中执行平移并且没有保存和恢复 canvas 状态,你可能会错过部分图形,因为这些图形绘制在 canvas 边缘之外。
一个 translate
的例子
此示例演示了平移 canvas 原点的一些好处。如果没有 translate()
方法,所有矩形都将在相同位置 (0,0) 绘制。translate()
方法还使我们能够在 canvas 上的任意位置放置矩形,而无需手动调整 fillRect()
函数中的坐标。这使它更容易理解和使用。
在 draw()
函数中,我们使用两个 for
循环九次调用 fillRect()
函数。在每个循环中,canvas 被平移,矩形被绘制,canvas 被返回到其原始状态。注意 fillRect()
的调用每次使用相同的坐标,依赖 translate()
来调整绘制位置。
function draw() {
const ctx = document.getElementById("canvas").getContext("2d");
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
ctx.save();
ctx.fillStyle = `rgb(${51 * i} ${255 - 51 * i} 255)`;
ctx.translate(10 + j * 50, 10 + i * 50);
ctx.fillRect(0, 0, 25, 25);
ctx.restore();
}
}
}
旋转
第二个变换方法是 rotate()
。我们用它来绕当前原点旋转 canvas。
rotate(angle)
-
将 canvas 绕当前原点顺时针旋转
angle
个弧度。
旋转中心点始终是 canvas 原点。若要更改中心点,我们需要使用 translate()
方法移动 canvas。
一个 rotate
的例子
在此示例中,我们将使用 rotate()
方法首先从 canvas 原点旋转矩形,然后借助 translate()
从矩形的中心旋转矩形。
**注意:**角度以弧度为单位,而不是度数。若要转换,我们使用:radians = (Math.PI/180)*degrees
。
function draw() {
const ctx = document.getElementById("canvas").getContext("2d");
// left rectangles, rotate from canvas origin
ctx.save();
// blue rect
ctx.fillStyle = "#0095DD";
ctx.fillRect(30, 30, 100, 100);
ctx.rotate((Math.PI / 180) * 25);
// grey rect
ctx.fillStyle = "#4D4E53";
ctx.fillRect(30, 30, 100, 100);
ctx.restore();
// right rectangles, rotate from rectangle center
// draw blue rect
ctx.fillStyle = "#0095DD";
ctx.fillRect(150, 30, 100, 100);
ctx.translate(200, 80); // translate to rectangle center
// x = x + 0.5 * width
// y = y + 0.5 * height
ctx.rotate((Math.PI / 180) * 25); // rotate
ctx.translate(-200, -80); // translate back
// draw grey rect
ctx.fillStyle = "#4D4E53";
ctx.fillRect(150, 30, 100, 100);
}
若要绕矩形自身中心旋转矩形,我们将 canvas 平移到矩形的中心,然后旋转 canvas,然后将 canvas 平移回 0,0,然后绘制矩形。
缩放
下一个变换方法是缩放。我们使用它来增加或减小 canvas 网格中的单位。这可以用来绘制缩小或放大的形状和位图。
scale(x, y)
-
将 canvas 单位水平缩放 x 倍,垂直缩放 y 倍。这两个参数都是实数。小于 1.0 的值会减小单位大小,大于 1.0 的值会增加单位大小。1.0 的值会保持单位大小不变。
使用负数可以进行轴镜像(例如,使用 translate(0,canvas.height); scale(1,-1);
你将拥有众所周知的笛卡尔坐标系,原点位于左下角)。
默认情况下,canvas 上的一个单位正好是一个像素。如果我们应用,例如,0.5 的缩放因子,那么得到的单位将变成 0.5 个像素,因此形状将被绘制为一半大小。类似地,将缩放因子设置为 2.0 会增加单位大小,一个单位现在变成两个像素。这会导致形状被绘制为两倍大小。
一个 scale
的例子
在此最后一个示例中,我们将绘制具有不同缩放因子的形状。
function draw() {
const ctx = document.getElementById("canvas").getContext("2d");
// draw a simple rectangle, but scale it.
ctx.save();
ctx.scale(10, 3);
ctx.fillRect(1, 10, 10, 10);
ctx.restore();
// mirror horizontally
ctx.scale(-1, 1);
ctx.font = "48px serif";
ctx.fillText("MDN", -135, 120);
}
变换
最后,以下变换方法允许直接修改变换矩阵。
transform(a, b, c, d, e, f)
-
将当前变换矩阵与由其参数描述的矩阵相乘。变换矩阵由下式描述
如果任何参数为
Infinity
,则变换矩阵必须被标记为无限,而不是方法抛出异常。
此函数的参数为:
a
(m11
)-
水平缩放。
b
(m12
)-
水平倾斜。
c
(m21
)-
垂直倾斜。
d
(m22
)-
垂直缩放。
e
(dx
)-
水平移动。
f
(dy
)-
垂直移动。
setTransform(a, b, c, d, e, f)
-
将当前变换重置为单位矩阵,然后使用相同的参数调用
transform()
方法。这基本上是撤消当前变换,然后设置指定的变换,所有操作都在一步完成。 resetTransform()
-
将当前变换重置为单位矩阵。这与调用
ctx.setTransform(1, 0, 0, 1, 0, 0);
相同。
transform
和 setTransform
的示例
function draw() {
const ctx = document.getElementById("canvas").getContext("2d");
const sin = Math.sin(Math.PI / 6);
const cos = Math.cos(Math.PI / 6);
ctx.translate(100, 100);
let c = 0;
for (let i = 0; i <= 12; i++) {
c = Math.floor((255 / 12) * i);
ctx.fillStyle = `rgb(${c} ${c} ${c})`;
ctx.fillRect(0, 0, 100, 10);
ctx.transform(cos, sin, -sin, cos, 0, 0);
}
ctx.setTransform(-1, 0, 0, 1, 100, 100);
ctx.fillStyle = "rgb(255 128 255 / 50%)";
ctx.fillRect(0, 50, 100, 100);
}