轉換

在本教程的早期,我們學習了畫布網格座標空間。到目前為止,我們只使用了預設的網格,並根據我們的需要更改了整個畫布的大小。透過變換,有更強大的方法可以將原點平移到不同的位置、旋轉網格甚至縮放網格。

儲存和恢復狀態

在檢視變換方法之前,讓我們先看另外兩種方法,一旦你開始生成更復雜的繪圖,這兩種方法將是不可或缺的。

save()

儲存畫布的整個狀態。

restore()

恢復最近儲存的畫布狀態。

畫布狀態儲存在堆疊中。每次呼叫 save() 方法時,當前繪圖狀態都會被推入堆疊。繪圖狀態包括:

您可以根據需要多次呼叫 save() 方法。每次呼叫 restore() 方法時,都會從堆疊中彈出最後一個儲存的狀態,並恢復所有儲存的設定。

儲存和恢復畫布狀態的示例

js
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 = "#0099ff"; // 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 = "white"; // 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()。此方法用於將畫布及其原點移動到網格中的不同點。

translate(x, y)

在網格上移動畫布及其原點。x 表示水平移動的距離,y 表示垂直移動網格的距離。

The canvas is pushed down and to the right, or translated, from its origin point on the grid by 'x' units horizontally and 'y' units vertically.

在進行任何變換之前儲存畫布狀態是一個好主意。在大多數情況下,呼叫 restore 方法比執行反向平移以返回原始狀態要容易得多。此外,如果您在迴圈中進行平移而不儲存和恢復畫布狀態,您可能會丟失部分繪圖,因為它繪製到了畫布邊緣之外。

translate 示例

此示例演示了平移畫布原點的一些好處。如果沒有 translate() 方法,所有矩形都將繪製在相同的位置 (0,0)。translate() 方法還使我們能夠將矩形放置在畫布上的任何位置,而無需在 fillRect() 函式中手動調整座標。這使其更容易理解和使用。

draw() 函式中,我們使用兩個 for 迴圈呼叫 fillRect() 函式九次。在每個迴圈中,畫布都會被平移,繪製矩形,然後畫布會恢復到其原始狀態。請注意 fillRect() 的呼叫每次都使用相同的座標,依靠 translate() 來調整繪圖位置。

js
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()。我們使用它圍繞當前原點旋轉畫布。

rotate(angle)

圍繞當前原點以 angle 弧度順時針旋轉畫布。

The default origin point is at the top left, 0 degrees is horizontal and to the right. The rotation point starts from the origin point and goes clockwise.

旋轉中心點始終是畫布原點。要更改中心點,我們需要使用 translate() 方法移動畫布。

rotate 示例

在此示例中,我們將使用 rotate() 方法,首先圍繞畫布原點旋轉一個矩形,然後藉助 translate() 圍繞矩形中心旋轉。

注意:角度以弧度為單位,而不是度。要進行轉換,我們使用:radians = (Math.PI/180)*degrees

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);
}

要圍繞矩形自身中心旋轉矩形,我們將畫布平移到矩形中心,然後旋轉畫布,然後將畫布平移回 0,0,然後繪製矩形。

縮放

下一個變換方法是縮放。我們使用它來增加或減少畫布網格中的單位。這可用於繪製縮小或放大的形狀和點陣圖。

scale(x, y)

在水平方向上按 x,在垂直方向上按 y 縮放畫布單位。這兩個引數都是實數。小於 1.0 的值會減小單位大小,大於 1.0 的值會增大單位大小。值為 1.0 時單位大小不變。

使用負數可以實現軸映象(例如,使用 translate(0,canvas.height); scale(1,-1); 將獲得眾所周知的笛卡爾座標系,原點位於左下角)。

預設情況下,畫布上的一個單位正好是一個畫素。如果我們應用,例如,縮放因子 0.5,則生成的單位將是 0.5 畫素,因此形狀將以一半大小繪製。類似地,將縮放因子設定為 2.0 將增加單位大小,一個單位現在變成兩個畫素。這會導致形狀以兩倍大小繪製。

scale 示例

在最後一個示例中,我們將繪製具有不同縮放因子的形狀。

js
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)

將當前變換矩陣與由其引數描述的矩陣相乘。變換矩陣由以下公式描述:

[acebdf001]\left[ \begin{array}{ccc} a & c & e \\ b & d & f \\ 0 & 0 & 1 \end{array} \right]

如果任何引數為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);

transformsetTransform 示例

js
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);
}