-
Notifications
You must be signed in to change notification settings - Fork 8k
/
index.md
278 lines (198 loc) · 12.8 KB
/
index.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
---
title: 变形 Transformations
slug: Web/API/Canvas_API/Tutorial/Transformations
---
{{DefaultAPISidebar("Canvas API")}} {{PreviousNext("Web/API/Canvas_API/Tutorial/Using_images", "Web/API/Canvas_API/Tutorial/Compositing")}}
在本教程前面的部分中,我们已经了解了 Canvas 网格和坐标空间。到目前为止,我们只是根据我们的需要使用默认的网格,改变整个画布的大小。变形是一种更强大的方法,可以将原点移动到另一点、对网格进行旋转和缩放。
## 状态的保存和恢复
在了解变形之前,我先介绍两个在你开始绘制复杂图形时必不可少的方法。
- {{domxref("CanvasRenderingContext2D.save", "save()")}}
- : 保存画布 (canvas) 的所有状态
- {{domxref("CanvasRenderingContext2D.restore", "restore()")}}
- : save 和 restore 方法是用来保存和恢复 canvas 状态的,都没有参数。Canvas 的状态就是当前画面应用的所有样式和变形的一个快照。
Canvas 状态存储在栈中,每当`save()`方法被调用后,当前的状态就被推送到栈中保存。一个绘画状态包括:
- 当前应用的变形(即移动,旋转和缩放,见下)
- 以及下面这些属性:{{domxref("CanvasRenderingContext2D.strokeStyle", "strokeStyle")}}, {{domxref("CanvasRenderingContext2D.fillStyle", "fillStyle")}}, {{domxref("CanvasRenderingContext2D.globalAlpha", "globalAlpha")}}, {{domxref("CanvasRenderingContext2D.lineWidth", "lineWidth")}}, {{domxref("CanvasRenderingContext2D.lineCap", "lineCap")}}, {{domxref("CanvasRenderingContext2D.lineJoin", "lineJoin")}}, {{domxref("CanvasRenderingContext2D.miterLimit", "miterLimit")}}, {{domxref("CanvasRenderingContext2D.lineDashOffset", "lineDashOffset")}}, {{domxref("CanvasRenderingContext2D.shadowOffsetX", "shadowOffsetX")}}, {{domxref("CanvasRenderingContext2D.shadowOffsetY", "shadowOffsetY")}}, {{domxref("CanvasRenderingContext2D.shadowBlur", "shadowBlur")}}, {{domxref("CanvasRenderingContext2D.shadowColor", "shadowColor")}}, {{domxref("CanvasRenderingContext2D.globalCompositeOperation", "globalCompositeOperation")}}, {{domxref("CanvasRenderingContext2D.font", "font")}}, {{domxref("CanvasRenderingContext2D.textAlign", "textAlign")}}, {{domxref("CanvasRenderingContext2D.textBaseline", "textBaseline")}}, {{domxref("CanvasRenderingContext2D.direction", "direction")}}, {{domxref("CanvasRenderingContext2D.imageSmoothingEnabled", "imageSmoothingEnabled")}}
- 当前的[裁切路径(clipping path)](/zh-CN/docs/Web/API/Canvas_API/Tutorial/Compositing#Clipping_paths),会在下一节介绍
你可以调用任意多次 `save`方法。每一次调用 `restore` 方法,上一个保存的状态就从栈中弹出,所有设定都恢复。
### `save` 和 `restore` 的应用示例
我们尝试用这个连续矩形的例子来描述 canvas 的状态栈是如何工作的。
第一步是用默认设置画一个大四方形,然后保存一下状态。改变填充颜色画第二个小一点的蓝色四方形,然后再保存一下状态。再次改变填充颜色绘制更小一点的半透明的白色四方形。
到目前为止所做的动作和前面章节的都很类似。不过一旦我们调用 `restore`,状态栈中最后的状态会弹出,并恢复所有设置。如果不是之前用 `save` 保存了状态,那么我们就需要手动改变设置来回到前一个状态,这个对于两三个属性的时候还是适用的,一旦多了,我们的代码将会猛涨。
当第二次调用 `restore` 时,已经恢复到最初的状态,因此最后是再一次绘制出一个黑色的四方形。
{{EmbedLiveSample("save 和 restore 的应用示例", "", "160")}}
```js
function draw() {
var ctx = document.getElementById("canvas").getContext("2d");
ctx.fillRect(0, 0, 150, 150); // 使用默认设置绘制一个矩形
ctx.save(); // 保存默认状态
ctx.fillStyle = "#09F"; // 在原有配置基础上对颜色做改变
ctx.fillRect(15, 15, 120, 120); // 使用新的设置绘制一个矩形
ctx.save(); // 保存当前状态
ctx.fillStyle = "#FFF"; // 再次改变颜色配置
ctx.globalAlpha = 0.5;
ctx.fillRect(30, 30, 90, 90); // 使用新的配置绘制一个矩形
ctx.restore(); // 重新加载之前的颜色状态
ctx.fillRect(45, 45, 60, 60); // 使用上一次的配置绘制一个矩形
ctx.restore(); // 加载默认颜色配置
ctx.fillRect(60, 60, 30, 30); // 使用加载的配置绘制一个矩形
}
```
```html hidden
<canvas id="canvas" width="150" height="150"></canvas>
```
```js hidden
draw();
```
## 移动
我们先介绍 `translate` 方法,它用来移动 canvas 和它的原点到一个不同的位置。
- `translate(x, y)`
- : `translate`方法接受两个参数。*x *是左右偏移量,_y_ 是上下偏移量,如右图所示。
![画布从网格上的原点水平向下向右平移“x”单位,垂直移动“y”单位。](canvas_grid_translate.png)
在做变形之前先保存状态是一个良好的习惯。大多数情况下,调用 restore 方法比手动恢复原先的状态要简单得多。又,如果你是在一个循环中做位移但没有保存和恢复 canvas 的状态,很可能到最后会发现怎么有些东西不见了,那是因为它很可能已经超出 canvas 范围以外了。
### `translate` 的示例
这个例子显示了一些移动 canvas 原点的好处。如果不使用 `translate` 方法,那么所有矩形都将被绘制在相同的位置(0,0)。`translate` 方法同时让我们可以任意放置这些图案,而不需要在 `fillRect()` 方法中手工调整坐标值,既好理解也方便使用。
我在 `draw` 方法中调用 `fillRect()` 方法 9 次,用了 2 层循环。每一次循环,先移动 canvas,画螺旋图案,然后恢复到原始状态。
```js
function draw() {
var ctx = document.getElementById("canvas").getContext("2d");
for (var i = 0; i < 3; i++) {
for (var 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();
}
}
}
```
```html hidden
<canvas id="canvas" width="150" height="150"></canvas>
```
```js hidden
draw();
```
{{EmbedLiveSample("translate 的示例", "", "160")}}
## 旋转 Rotating
![](canvas_grid_rotate.png)
第二个介绍 `rotate`方法,它用于以原点为中心旋转 canvas。
- `rotate(angle)`
- : 这个方法只接受一个参数:旋转的角度 (angle),它是顺时针方向的,以弧度为单位的值。
旋转的中心点始终是 canvas 的原点,如果要改变它,我们需要用到 `translate`方法。
### `rotate` 的示例
在这个例子里,我们将会使用 `rotate()` 方法来画圆并构成圆形图案。当然你也可以分别计算出 _x_ 和 _y_ 坐标(`x = r*Math.cos(a); y = r*Math.sin(a)`)。这里无论用什么方法都无所谓的,因为我们画的是圆。计算坐标的结果只是旋转圆心位置,而不是圆本身。即使用 `rotate`旋转两者,那些圆看上去还是一样的,不管它们绕中心旋转有多远。
这里我们又用到了两层循环。第一层循环决定环的数量,第二层循环决定每环有多少个点。每环开始之前,我都保存一下 canvas 的状态,这样恢复起来方便。每次画圆点,我都以一定夹角来旋转 canvas,而这个夹角则是由环上的圆点数目的决定的。最里层的环有 6 个圆点,这样,每次旋转的夹角就是 360/6 = 60 度。往外每一环的圆点数目是里面一环的 2 倍,那么每次旋转的夹角随之减半。
```js
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);
}
```
```html hidden
<canvas id="canvas" width="300" height="200"></canvas>
```
```js hidden
draw();
```
{{EmbedLiveSample("rotate 的示例", "", "220")}}
## 缩放
接着是缩放。我们用它来增减图形在 canvas 中的像素数目,对形状,位图进行缩小或者放大。
- **`scale(x, y)`**
- : `scale`方法可以缩放画布的水平和垂直的单位。两个参数都是实数,可以为负数,x 为水平缩放因子,y 为垂直缩放因子,如果比 1 小,会缩小图形,如果比 1 大会放大图形。默认值为 1,为实际大小。
画布初始情况下,是以左上角坐标为原点的第一象限。如果参数为负实数,相当于以 x 或 y 轴作为对称轴镜像反转(例如,使用`translate(0,canvas.height); scale(1,-1);` 以 y 轴作为对称轴镜像反转,就可得到著名的笛卡尔坐标系,左下角为原点)。
默认情况下,canvas 的 1 个单位为 1 个像素。举例说,如果我们设置缩放因子是 0.5,1 个单位就变成对应 0.5 个像素,这样绘制出来的形状就会是原先的一半。同理,设置为 2.0 时,1 个单位就对应变成了 2 像素,绘制的结果就是图形放大了 2 倍。
### `scale` 的示例
这最后的例子里,我们用不同的缩放方式来画两个图形。
```js
function draw() {
var 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);
}
```
```html hidden
<canvas id="canvas" width="150" height="150"></canvas>
```
```js hidden
draw();
```
{{EmbedLiveSample("scale 的示例", "", "160")}}
## 变形
最后一个方法允许对变形矩阵直接修改。
- {{domxref("CanvasRenderingContext2D.transform", "transform(a, b, c, d, e, f)")}}
- : 这个方法是将当前的变形矩阵乘上一个基于自身参数的矩阵,如下面的矩阵所示:
<math><semantics><mrow><mo>[</mo><mtable columnalign="center center center" rowspacing="0.5ex"><mtr><mtd><mi>a</mi></mtd><mtd><mi>c</mi></mtd><mtd><mi>e</mi></mtd></mtr><mtr><mtd><mi>b</mi></mtd><mtd><mi>d</mi></mtd><mtd><mi>f</mi></mtd></mtr><mtr><mtd><mn>0</mn></mtd><mtd><mn>0</mn></mtd><mtd><mn>1</mn></mtd></mtr></mtable><mo>]</mo></mrow><annotation encoding="TeX">\left[ \begin{array}{ccc} a & c & e \\ b & d & f \\ 0 & 0 & 1 \end{array} \right]</annotation></semantics></math>
如果任意一个参数是 [`Infinity`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Infinity),变形矩阵也必须被标记为无限大,否则会抛出异常。
这个函数的参数各自代表如下:
- `a (m11)`
- : 水平方向的缩放
- `b(m12)`
- : 竖直方向的倾斜偏移
- `c(m21)`
- : 水平方向的倾斜偏移
- `d(m22)`
- : 竖直方向的缩放
- `e(dx)`
- : 水平方向的移动
- `f(dy)`
- : 竖直方向的移动
- {{domxref("CanvasRenderingContext2D.setTransform", "setTransform(a, b, c, d, e, f)")}}
- : 这个方法会将当前的变形矩阵重置为单位矩阵,然后用相同的参数调用 `transform`方法。如果任意一个参数是无限大,那么变形矩阵也必须被标记为无限大,否则会抛出异常。从根本上来说,该方法是取消了当前变形,然后设置为指定的变形,一步完成。
- {{domxref("CanvasRenderingContext2D.resetTransform", "resetTransform()")}}
- : 重置当前变形为单位矩阵,它和调用以下语句是一样的:`ctx.setTransform(1, 0, 0, 1, 0, 0);`
### `transform` 和 `setTransform` 的示例
```js
function draw() {
var ctx = document.getElementById("canvas").getContext("2d");
var sin = Math.sin(Math.PI / 6);
var cos = Math.cos(Math.PI / 6);
ctx.translate(100, 100);
var c = 0;
for (var 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 = "rgba(255, 128, 255, 0.5)";
ctx.fillRect(0, 50, 100, 100);
}
```
```html hidden
<canvas id="canvas" width="200" height="250"></canvas>
```
```js hidden
draw();
```
{{EmbedLiveSample("transform 和 setTransform 的示例", "", "260")}}
{{PreviousNext("Web/API/Canvas_API/Tutorial/Using_images", "Web/API/Canvas_API/Tutorial/Compositing")}}