变形 Transformations



    状态的保存和恢复 Saving and restoring state

    Before we look at the transformation methods, I'll introduce two other methods which are indispensable once you start generating ever more complex drawings.



    The canvas save and restore methods are used to save and retrieve the canvas state. The canvas drawing state is basically a snapshot of all the styles and transformations that have been applied. Both methods take no parameters.
    saverestore 方法是用来保存和恢复 canvas 状态的,都没有参数。Canvas 的状态就是当前画面应用的所有样式和变形的一个快照。

    Canvas states are stored on a stack. Every time the save method is called, the current drawing state is pushed onto the stack. A drawing state consists of
    Canvas 状态是以堆(stack)的方式保存的,每一次调用 save 方法,当前的状态就会被推入堆中保存起来。这种状态包括:

    • The transformations that have been applied (i.e. translate, rotate and scale - see below).
    • The values of strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation properties.
      strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation 的值
    • The current clipping path, which we'll see in the next section.
      当前的裁切路径(clipping path),会在下一节介绍

    You can call the save method as many times as you like.
    你可以调用任意多次 save 方法。

    Every time the restore method is called, the last saved state is returned from the stack and all saved settings are restored.
    每一次调用 restore 方法,上一个保存的状态就从堆中弹出,所有设定都恢复。

    save 和 restore 的应用例子

    This example tries to illustrate how the stack of drawing states functions by drawing a set of consecutive rectangles.
    我们尝试用这个连续矩形的例子来描述 canvas 的状态堆是如何工作的。

    The first step is to draw a large rectangle with the default settings. Next we save this state and make changes to the fill color. We then draw the second and smaller blue rectangle and save the state. Again we change some drawing settings and draw the third semi-transparent white rectangle.

    So far this is pretty similar to what we've done in previous sections. However once we call the first restore statement, the top drawing state is removed from the stack, and settings are restored. If we hadn't saved the state using save, we would need to change the fill color and transparency manually in order to return to the previous state. This would be easy for two properties, but if we have more than that, our code would become very long, very fast.
    到目前为止所做的动作和前面章节的都很类似。不过一旦我们调用 restore,状态堆中最后的状态会弹出,并恢复所有设置。如果不是之前用 save 保存了状态,那么我们就需要手动改变设置来回到前一个状态,这个对于两三个属性的时候还是适用的,一旦多了,我们的代码将会猛涨。

    When the second restore statement is called, the original state (the one we set up before the first call to save) is restored and the last rectangle is once again drawn in black.
    当第二次调用 restore 时,已经恢复到最初的状态,因此最后是再一次绘制出一个黑色的四方形。


    function draw() {
      var ctx = document.getElementById('canvas').getContext('2d');
      ctx.fillRect(0,0,150,150);   // Draw a rectangle with default settings;                  // Save the default state
      ctx.fillStyle = '#09F'       // Make changes to the settings
      ctx.fillRect(15,15,120,120); // Draw a rectangle with new settings
 ;                  // Save the current state
      ctx.fillStyle = '#FFF'       // Make changes to the settings
      ctx.globalAlpha = 0.5;    
      ctx.fillRect(30,30,90,90);   // Draw a rectangle with new settings
      ctx.restore();               // Restore previous state
      ctx.fillRect(45,45,60,60);   // Draw a rectangle with restored settings
      ctx.restore();               // Restore original state
      ctx.fillRect(60,60,30,30);   // Draw a rectangle with restored settings

    移动 Translating

    The first of the transformation methods we'll look at is translate. This method is used to move the canvas and its origin to a different point in the grid.

    我们先介绍 translate 方法,它用来移动 canvas 和它的原点到一个不同的位置。

    translate(x, y)

    This method takes two arguments. x is the amount the canvas is moved to the left or right, and y is the amount it's moved up or down (illustrated by the image on the right).

    translate 方法接受两个参数。x 是左右偏移量,y 是上下偏移量,如右图所示。

    It's a good idea to save the canvas state before doing any transformations. In most cases, it is just easier to call the restore method than having to do a reverse translation to return to the original state. Also if you're translating inside a loop and don't save and restore the canvas state, you might end up missing part of your drawing, because it was drawn outside the canvas edge.

    在做变形之前先保存状态是一个良好的习惯。大多数情况下,调用 restore 方法比手动恢复原先的状态要简单得多。又,如果你是在一个循环中做位移但没有保存和恢复 canvas 的状态,很可能到最后会发现怎么有些东西不见了,那是因为它很可能已经超出 canvas 范围以外了。

    translate 的例子

    This example demonstrates some of the benefits of translating the canvas origin. I've made a function drawSpirograph that draws spirograph patterns. These are drawn around the origin. If I didn't use the translate function, I would only see a quarter of the pattern on the canvas. The translate method also gives me the freedom to place it anywhere on the canvas without having to manually adjust coordinates in the spirograph function. This makes it a little easier to understand and use.

    这个例子显示了一些移动 canvas 原点的好处。我创建了一个 drawSpirograph 方法用来绘制螺旋(spirograph)图案,那是围绕原点绘制出来的。如果不使用 translate 方法,那么只能看见其中的四分之一。translate 同时让我可以任意放置这些图案,而不需要在 spirograph 方法中手工调整坐标值,既好理解也方便使用。

    In the draw function I call the drawSpirograph nine times using two for loops. In each loop the canvas is translated, the spirograph is drawn, and the canvas is returned back to its original state.

    我在 draw 方法中调用 drawSpirograph 方法 9 次,用了 2 层循环。每一次循环,先移动 canvas ,画螺旋图案,然后恢复早原始状态。


    function draw() {
      var ctx = document.getElementById('canvas').getContext('2d');
      for (var i=0;i<3;i++) {
        for (var j=0;j<3;j++) {
          ctx.strokeStyle = "#9CFF00";
    function drawSpirograph(ctx,R,r,O){
      var x1 = R-O;
      var y1 = 0;
      var i  = 1;
      do {
        if (i>20000) break;
        var x2 = (R+r)*Math.cos(i*Math.PI/72) - (r+O)*Math.cos(((R+r)/r)*(i*Math.PI/72))
        var y2 = (R+r)*Math.sin(i*Math.PI/72) - (r+O)*Math.sin(((R+r)/r)*(i*Math.PI/72))
        x1 = x2;
        y1 = y2;
      } while (x2 != R-O && y2 != 0 );

    旋转 Rotating

    The second transformation method is rotate. We use it to rotate the canvas around the current origin.

    第二个介绍 rotate 方法,它用于以原点为中心旋转 canvas。


    This method only takes one parameter and that's the angle the canvas is rotated by. This is a clockwise rotation measured in radians (illustrated in the image on the right).


    The rotation center point is always the canvas origin. To change the center point, we will need to move the canvas by using the translate method.

    旋转的中心点始终是 canvas 的原点,如果要改变它,我们需要用到 translate 方法。

    rotate 的例子

    In the example, you can see on the right, I used the rotate method to draw shapes in a circular pattern. You could also have calculated the individual x and y coordinates (x = r*Math.cos(a); y = r*Math.sin(a)). In this case it doesn't really matter which method you choose, because we're drawing circles. Calculating the coordinates results in only rotating the center positions of the circles and not the circles themselves, while using rotate results in both, but of course circles look the same no matter how far they are rotated about their centers.

    在这个例子里,见右图,我用 rotate 方法来画圆并构成圆形图案。当然你也可以分别计算出 xy 坐标(x = r*Math.cos(a); y = r*Math.sin(a))。这里无论用什么方法都无所谓的,因为我们画的是圆。计算坐标的结果只是旋转圆心位置,而不是圆本身。即使用 rotate 旋转两者,那些圆看上去还是一样的,不管它们绕中心旋转有多远。

    Again we have two loops. The first determines the number of rings, and the second determines the number of dots drawn in each ring. Before drawing each ring, I save the canvas state, so I can easily retrieve it. For each dot that is drawn, I rotate the canvas coordinate space by an angle that is determined by the number of dots in the ring. The innermost circle has six dots, so in each step, I rotate over an angle of 360/6 = 60 degrees. With each additional ring, the number of dots is doubled, and the angle in turn is halved.

    这里我们又用到了两层循环。第一层循环决定环的数量,第二层循环决定每环有多少个点。每环开始之前,我都保存一下 canvas 的状态,这样恢复起来方便。每次画圆点,我都以一定夹角来旋转 canvas,而这个夹角则是由环上的圆点数目的决定的。最里层的环有 6 个圆点,这样,每次旋转的夹角就是 360/6 = 60 度。往外每一环的圆点数目是里面一环的 2 倍,那么每次旋转的夹角随之减半。


    function draw() {
      var ctx = document.getElementById('canvas').getContext('2d');
      for (var i=1;i<6;i++){ // Loop through rings (from inside to out);
        ctx.fillStyle = 'rgb('+(51*i)+','+(255-51*i)+',255)';
        for (var j=0;j<i*6;j++){ // draw individual dots

    缩放 Scaling

    The next transformation method is scaling. We use it to increase or decrease the units in our canvas grid. This can be used to draw scaled down or enlarged shapes and bitmaps.

    接着是缩放。我们用它来增减图形在 canvas 中的像素数目,对形状,位图进行缩小或者放大。

    scale(x, y)

    This method takes two parameters. x is the scale factor in the horizontal direction and y is the scale factor in the vertical direction. Both parameters must be positive numbers. Values smaller than 1.0 reduce the unit size and values larger than 1.0 increase the unit size. Setting the scaling factor to precisely 1.0 doesn't affect the unit size.

    scale 方法接受两个参数。x,y 分别是横轴和纵轴的缩放因子,它们都必须是正值。值比 1.0 小表示缩小,比 1.0 大则表示放大,值为 1.0 时什么效果都没有。

    By default one unit on the canvas is exactly one pixel. If we apply, for instance, a scaling factor of 0.5, the resulting unit would become 0.5 pixels and so shapes would be drawn at half size. In a similar way setting the scaling factor to 2.0 would increase the unit size and one unit now becomes two pixels. This results in shapes being drawn twice as large.

    默认情况下,canvas 的 1 单位就是 1 个像素。举例说,如果我们设置缩放因子是 0.5,1 个单位就变成对应 0.5 个像素,这样绘制出来的形状就会是原先的一半。同理,设置为 2.0 时,1 个单位就对应变成了 2 像素,绘制的结果就是图形放大了 2 倍。

    scale 的例子

    In this last example I've used the spirograph function from one of the previous examples to draw nine shapes with different scaling factors. The top left shape has been drawn with no scaling applied. The yellow shapes to the right both have a uniform scaling factor (the same value for x and y parameters). If you look at the code below you'll see that I've used the scale method twice with equal parameter values for the second and third spirograph. Because I didn't restore the canvas state, the third shape is drawn with a scaling factor of 0.75 × 0.75 = 0.5625.

    这最后的例子里,我再次启用前面曾经用过的 spirograph 方法,来画 9 个图形,分别赋予不同的缩放因子。左上角的图形是未经缩放的。黄色图案从左到右应用了统一的缩放因子(x 和 y 参数值是一致的)。看下面的代码,你可以发现,我在画第二第三个图案时 scale 了两次,中间没有 restore canvas 的状态,因此第三个图案的缩放因子其实是 0.75 × 0.75 = 0.5625。

    The second row of blue shapes have a non-uniform scaling applied in a vertical direction. Each of the shapes has the x scaling factor set to 1.0 which means no scaling. The y scaling factor is set to 0.75. This results in the three shapes being squashed down. The original circular shape has now become an ellipse. If you look closely you'll see that the line width has also been reduced in the vertical direction.

    第二行蓝色图案堆垂直方向应用了不统一的缩放因子,每个图形 x 方向上的缩放因子都是 1.0,意味着不缩放,而 y 方向缩放因子是 0.75,得出来的结果是,图案被依次压扁了。原来的圆形图案变成了椭圆,如果细心观察,还可以发现在垂直方向上的线宽也减少了。

    The third row of green shapes is similar to the one above but now I've applied a scaling in the horizontal direction.



    function draw() {
      var ctx = document.getElementById('canvas').getContext('2d');
      ctx.strokeStyle = "#fc0";
      ctx.lineWidth = 1.5;
      // Uniform scaling
      drawSpirograph(ctx,22,6,5);  // no scaling
      // Non-uniform scaling (y direction)
      ctx.strokeStyle = "#0cf";
      // Non-uniform scaling (x direction)
      ctx.strokeStyle = "#cf0";

    变形 Transforms

    The final transformation methods allow modifications directly to the transformation matrix.


    transform(m11, m12, m21, m22, dx, dy)

    This method must multiply the current transformation matrix with the matrix described by:


    m11 	m21 	dx
    m12 	m22 	dy
    0 	0 	1

    If any of the arguments are Infinity the transformation matrix must be marked as infinite instead of the method throwing an exception.


    setTransform(m11, m12, m21, m22, dx, dy)

    This method must reset the current transform to the identity matrix, and then invoke the transform method with the same arguments. If any of the arguments are Infinity the transformation matrix must be marked as infinite instead of the method throwing an exception.

    这个方法必须重置当前的变形矩阵为单位矩阵,然后以相同的参数调用 transform 方法。如果任意一个参数是无限大,那么变形矩阵也必须被标记为无限大,否则会抛出异常。

    transform / setTransform 的例子

    function draw() {
      var canvas = document.getElementById("canvas");
      var ctx = canvas.getContext("2d");
      var sin = Math.sin(Math.PI/6);
      var cos = Math.cos(Math.PI/6);
      ctx.translate(200, 200);
      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, 200, 200);
      ctx.fillStyle = "rgba(255, 128, 255, 0.5)";
      ctx.fillRect(0, 50, 100, 100);


    Contributors to this page: YanLing, zhongsheng, ziyunfei, leegorous, vinqon
    最后编辑者: ziyunfei,