aboutsummaryrefslogtreecommitdiff
path: root/files/zh-cn/web/api/canvas_api/tutorial/transformations
diff options
context:
space:
mode:
authorPeter Bengtsson <mail@peterbe.com>2020-12-08 14:40:17 -0500
committerPeter Bengtsson <mail@peterbe.com>2020-12-08 14:40:17 -0500
commit33058f2b292b3a581333bdfb21b8f671898c5060 (patch)
tree51c3e392513ec574331b2d3f85c394445ea803c6 /files/zh-cn/web/api/canvas_api/tutorial/transformations
parent8b66d724f7caf0157093fb09cfec8fbd0c6ad50a (diff)
downloadtranslated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.gz
translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.bz2
translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.zip
initial commit
Diffstat (limited to 'files/zh-cn/web/api/canvas_api/tutorial/transformations')
-rw-r--r--files/zh-cn/web/api/canvas_api/tutorial/transformations/index.html278
1 files changed, 278 insertions, 0 deletions
diff --git a/files/zh-cn/web/api/canvas_api/tutorial/transformations/index.html b/files/zh-cn/web/api/canvas_api/tutorial/transformations/index.html
new file mode 100644
index 0000000000..d8e65e4dc4
--- /dev/null
+++ b/files/zh-cn/web/api/canvas_api/tutorial/transformations/index.html
@@ -0,0 +1,278 @@
+---
+title: 变形 Transformations
+slug: Web/API/Canvas_API/Tutorial/Transformations
+tags:
+ - Canvas
+ - HTML
+ - HTML5
+ - Web
+ - 图形
+ - 教程
+translation_of: Web/API/Canvas_API/Tutorial/Transformations
+---
+<div>{{CanvasSidebar}} {{PreviousNext("Web/API/Canvas_API/Tutorial/Using_images", "Web/API/Canvas_API/Tutorial/Compositing")}}</div>
+
+<p class="summary">在本教程前面的部分中,我们已经了解了Canvas网格和坐标空间。到目前为止,我们只是根据我们的需要使用默认的网格,改变整个画布的大小。变形是一种更强大的方法,可以将原点移动到另一点、对网格进行旋转和缩放。</p>
+
+<h2 id="Saving_and_restoring_state" name="Saving_and_restoring_state">状态的保存和恢复 Saving and restoring state</h2>
+
+<p>在了解变形之前,我先介绍两个在你开始绘制复杂图形时必不可少的方法。</p>
+
+<dl>
+ <dt>{{domxref("CanvasRenderingContext2D.save", "save()")}}</dt>
+ <dd>保存画布(canvas)的所有状态</dd>
+ <dt>{{domxref("CanvasRenderingContext2D.restore", "restore()")}}</dt>
+ <dd>save 和 restore 方法是用来保存和恢复 canvas 状态的,都没有参数。Canvas 的状态就是当前画面应用的所有样式和变形的一个快照。</dd>
+</dl>
+
+<p><span style="line-height: 1.5;">Canvas状态存储在栈中,每当<code>save()</code>方法被调用后,当前的状态就被推送到栈中保存。一个绘画状态包括:</span></p>
+
+<ul>
+ <li>当前应用的变形(即移动,旋转和缩放,见下)</li>
+ <li>以及下面这些属性:{{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")}}</li>
+ <li>当前的<a href="/zh-CN/docs/Web/API/Canvas_API/Tutorial/Compositing#Clipping_paths">裁切路径(clipping path)</a>,会在下一节介绍</li>
+</ul>
+
+<p>你可以调用任意多次 <code>save</code>方法。每一次调用 <code>restore</code> 方法,上一个保存的状态就从栈中弹出,所有设定都恢复。</p>
+
+<h3 id="A_save_and_restore_canvas_state_example" name="A_save_and_restore_canvas_state_example"><code>save</code> 和 <code>restore</code> 的应用例子</h3>
+
+<p><img alt="" class="internal" src="/@api/deki/files/104/=Canvas_savestate.png" style="float: right;">我们尝试用这个连续矩形的例子来描述 canvas 的状态栈是如何工作的。</p>
+
+<p>第一步是用默认设置画一个大四方形,然后保存一下状态。改变填充颜色画第二个小一点的蓝色四方形,然后再保存一下状态。再次改变填充颜色绘制更小一点的半透明的白色四方形。</p>
+
+<p>到目前为止所做的动作和前面章节的都很类似。不过一旦我们调用 <code>restore</code>,状态栈中最后的状态会弹出,并恢复所有设置。如果不是之前用 <code>save </code>保存了状态,那么我们就需要手动改变设置来回到前一个状态,这个对于两三个属性的时候还是适用的,一旦多了,我们的代码将会猛涨。</p>
+
+<p>当第二次调用 <code>restore</code> 时,已经恢复到最初的状态,因此最后是再一次绘制出一个黑色的四方形。</p>
+
+<p>{{EmbedLiveSample("A_save_and_restore_canvas_state_example", "180", "180", "https://mdn.mozillademos.org/files/249/Canvas_savestate.png")}}</p>
+
+<pre class="brush: js notranslate">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); // 使用加载的配置绘制一个矩形
+}
+</pre>
+
+<div class="hidden">
+<pre class="brush: html notranslate">&lt;canvas id="canvas" width="150" height="150"&gt;&lt;/canvas&gt;</pre>
+
+<pre class="brush: js notranslate">draw();</pre>
+</div>
+
+<h2 id="Translating" name="Translating">移动 Translating</h2>
+
+<p><img alt="" class="internal" src="/@api/deki/files/85/=Canvas_grid_translate.png" style="float: right;"></p>
+
+<p>我们先介绍 <code>translate </code>方法,它用来移动 canvas 和它的原点到一个不同的位置。</p>
+
+<dl>
+ <dt><code>translate(x, y)</code></dt>
+ <dd><code>translate </code>方法接受两个参数。<var>x </var>是左右偏移量,<var>y</var> 是上下偏移量,如右图所示。</dd>
+</dl>
+
+<p>在做变形之前先保存状态是一个良好的习惯。大多数情况下,调用 restore 方法比手动恢复原先的状态要简单得多。又,如果你是在一个循环中做位移但没有保存和恢复 canvas 的状态,很可能到最后会发现怎么有些东西不见了,那是因为它很可能已经超出 canvas 范围以外了。</p>
+
+<h3 id="A_translate_example" name="A_translate_example"><code>translate</code> 的例子</h3>
+
+<p>这个例子显示了一些移动 canvas 原点的好处。如果不使用 <code>translate </code>方法,那么所有矩形都将被绘制在相同的位置(0,0)。<code>translate </code>方法同时让我们可以任意放置这些图案,而不需要在 <code>fillRect()</code> 方法中手工调整坐标值,既好理解也方便使用。</p>
+
+<p>我在 <code>draw </code>方法中调用 <code>fillRect()</code> 方法 9 次,用了 2 层循环。每一次循环,先移动 canvas ,画螺旋图案,然后恢复到原始状态。</p>
+
+<pre class="brush: js; highlight:[7] notranslate">function draw() {
+ var ctx = document.getElementById('canvas').getContext('2d');
+ for (var i = 0; i &lt; 3; i++) {
+ for (var j = 0; j &lt; 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();
+ }
+ }
+}
+</pre>
+
+<div class="hidden">
+<pre class="brush: html notranslate">&lt;canvas id="canvas" width="150" height="150"&gt;&lt;/canvas&gt;</pre>
+
+<pre class="brush: js notranslate">draw();</pre>
+</div>
+
+<p>{{EmbedLiveSample("A_translate_example", "160", "160", "https://mdn.mozillademos.org/files/9857/translate.png")}}</p>
+
+<h2 id="Rotating" name="Rotating">旋转 Rotating</h2>
+
+<p><img alt="" class="internal" src="/@api/deki/files/84/=Canvas_grid_rotate.png" style="float: right;"></p>
+
+<p>第二个介绍 <code>rotate </code>方法,它用于以原点为中心旋转 canvas。</p>
+
+<dl>
+ <dt><code>rotate(angle)</code></dt>
+ <dd>这个方法只接受一个参数:旋转的角度(angle),它是顺时针方向的,以弧度为单位的值。</dd>
+</dl>
+
+<p>旋转的中心点始终是 canvas 的原点,如果要改变它,我们需要用到 <code>translate </code>方法。</p>
+
+<h3 id="A_rotate_example" name="A_rotate_example"><code>rotate</code> 的例子</h3>
+
+<p><img alt="" class="internal" src="/@api/deki/files/103/=Canvas_rotate.png" style="float: right;"></p>
+
+<p>在这个例子里,见右图,我用 <code>rotate </code>方法来画圆并构成圆形图案。当然你也可以分别计算出 <var>x</var> 和 <var>y</var> 坐标(<code>x = r*Math.cos(a); y = r*Math.sin(a)</code>)。这里无论用什么方法都无所谓的,因为我们画的是圆。计算坐标的结果只是旋转圆心位置,而不是圆本身。即使用 <code>rotate </code>旋转两者,那些圆看上去还是一样的,不管它们绕中心旋转有多远。</p>
+
+<p>这里我们又用到了两层循环。第一层循环决定环的数量,第二层循环决定每环有多少个点。每环开始之前,我都保存一下 canvas 的状态,这样恢复起来方便。每次画圆点,我都以一定夹角来旋转 canvas,而这个夹角则是由环上的圆点数目的决定的。最里层的环有 6 个圆点,这样,每次旋转的夹角就是 360/6 = 60 度。往外每一环的圆点数目是里面一环的 2 倍,那么每次旋转的夹角随之减半。</p>
+
+<div class="hidden">
+<pre class="brush: html notranslate">&lt;canvas id="canvas" width="300" height="200"&gt;&lt;/canvas&gt;</pre>
+
+<pre class="brush: js notranslate">draw();</pre>
+</div>
+
+<p>{{EmbedLiveSample("A_rotate_example", "310", "210", "https://mdn.mozillademos.org/files/9859/rotate.png")}}</p>
+
+<pre class="brush: js notranslate">function draw() {
+ var ctx = document.getElementById('canvas').getContext('2d');
+ ctx.translate(75,75);
+
+ for (var i=1;i&lt;6;i++){ // Loop through rings (from inside to out)
+ ctx.save();
+ ctx.fillStyle = 'rgb('+(51*i)+','+(255-51*i)+',255)';
+
+ for (var j=0;j&lt;i*6;j++){ // draw individual dots
+ ctx.rotate(Math.PI*2/(i*6));
+ ctx.beginPath();
+ ctx.arc(0,i*12.5,5,0,Math.PI*2,true);
+ ctx.fill();
+ }
+
+ ctx.restore();
+ }
+}
+</pre>
+
+<h2 id="Scaling" name="Scaling">缩放 Scaling</h2>
+
+<p>接着是缩放。我们用它来增减图形在 canvas 中的像素数目,对形状,位图进行缩小或者放大。</p>
+
+<dl>
+ <dt><code><strong>scale(x, y)</strong></code></dt>
+ <dd><code>scale </code> 方法可以缩放画布的水平和垂直的单位。两个参数都是实数,可以为负数,x 为水平缩放因子,y 为垂直缩放因子,如果比1小,会缩小图形, 如果比1大会放大图形。默认值为1, 为实际大小。</dd>
+</dl>
+
+<p>画布初始情况下, 是以左上角坐标为原点的第一象限。如果参数为负实数, 相当于以x 或 y轴作为对称轴镜像反转(例如, 使用<code>translate(0,canvas.height); scale(1,-1);</code> 以y轴作为对称轴镜像反转, 就可得到著名的笛卡尔坐标系,左下角为原点)。</p>
+
+<p>默认情况下,canvas 的 1 个单位为 1 个像素。举例说,如果我们设置缩放因子是 0.5,1 个单位就变成对应 0.5 个像素,这样绘制出来的形状就会是原先的一半。同理,设置为 2.0 时,1 个单位就对应变成了 2 像素,绘制的结果就是图形放大了 2 倍。</p>
+
+<h3 id="A_scale_example" name="A_scale_example"><code>scale</code> 的例子</h3>
+
+<p>这最后的例子里,我们用不同的缩放方式来画两个图形。</p>
+
+<pre class="brush: js notranslate">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);
+}</pre>
+
+<div class="hidden">
+<pre class="brush: html notranslate">&lt;canvas id="canvas" width="150" height="150"&gt;&lt;/canvas&gt;</pre>
+
+<pre class="brush: js notranslate">draw();</pre>
+</div>
+
+<p>{{EmbedLiveSample("A_scale_example", "160", "160", "https://mdn.mozillademos.org/files/9861/scale.png")}}</p>
+
+<h2 id="Transforms" name="Transforms">变形 Transforms</h2>
+
+<p>最后一个方法允许对变形矩阵直接修改。</p>
+
+<dl>
+ <dt>{{domxref("CanvasRenderingContext2D.transform", "transform(a, b, c, d, e, f)")}}</dt>
+ <dd>这个方法是将当前的变形矩阵乘上一个基于自身参数的矩阵,如下面的矩阵所示:<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 &amp; c &amp; e \\ b &amp; d &amp; f \\ 0 &amp; 0 &amp; 1 \end{array} \right]</annotation></semantics></math></dd>
+ <dd>如果任意一个参数是<code><a href="https://wiki.developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Infinity" title="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Infinity">Infinity</a></code>,变形矩阵也必须被标记为无限大,否则会抛出异常。</dd>
+</dl>
+
+<p>这个函数的参数各自代表如下:</p>
+
+<dl>
+ <dt><code>a (m11)</code></dt>
+ <dd>水平方向的缩放</dd>
+ <dt><code>b(m12)</code></dt>
+ <dd>竖直方向的倾斜偏移</dd>
+ <dt><code>c(m21)</code></dt>
+ <dd>水平方向的倾斜偏移</dd>
+ <dt><code>d(m22)</code></dt>
+ <dd>竖直方向的缩放</dd>
+ <dt><code>e(dx)</code></dt>
+ <dd>水平方向的移动</dd>
+ <dt><code>f(dy)</code></dt>
+ <dd>竖直方向的移动</dd>
+</dl>
+
+<dl>
+ <dt>{{domxref("CanvasRenderingContext2D.setTransform", "setTransform(a, b, c, d, e, f)")}}</dt>
+ <dd>这个方法会将当前的变形矩阵重置为单位矩阵,然后用相同的参数调用 <code>transform </code>方法。如果任意一个参数是无限大,那么变形矩阵也必须被标记为无限大,否则会抛出异常。从根本上来说,该方法是取消了当前变形,然后设置为指定的变形,一步完成。</dd>
+</dl>
+
+<dl>
+ <dt>{{domxref("CanvasRenderingContext2D.resetTransform", "resetTransform()")}}</dt>
+ <dd>重置当前变形为单位矩阵,它和调用以下语句是一样的:<code>ctx.setTransform(1, 0, 0, 1, 0, 0);</code></dd>
+</dl>
+
+<h3 id="transform_setTransform_的例子"><code>transform</code> / <code>setTransform</code> 的例子</h3>
+
+<dl>
+</dl>
+
+<pre class="brush: js; highlight:[12,15] notranslate">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 &lt;= 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);
+}</pre>
+
+<div class="hidden">
+<pre class="brush: html notranslate">&lt;canvas id="canvas" width="200" height="250"&gt;&lt;/canvas&gt;</pre>
+
+<pre class="brush: js notranslate">draw();</pre>
+</div>
+
+<p>{{EmbedLiveSample("transform_setTransform_的例子", "230", "280", "https://mdn.mozillademos.org/files/255/Canvas_transform.png")}}</p>
+
+<p><br>
+ {{PreviousNext("Web/API/Canvas_API/Tutorial/Using_images", "Web/API/Canvas_API/Tutorial/Compositing")}}</p>