合成和裁剪

在我們之前的示例中,形狀始終是繪製在彼此之上。對於大多數情況來說,這已經足夠了,但它限制了複合形狀的構建順序。然而,我們可以透過設定 globalCompositeOperation 屬性來改變這種行為。此外,clip 屬性允許我們隱藏形狀中不需要的部分。

globalCompositeOperation

我們不僅可以繪製新形狀到現有形狀的後面,還可以用它來遮罩特定區域,從畫布中清除部分割槽域(不限於像 clearRect() 方法那樣是矩形),等等。

globalCompositeOperation = type

這會設定繪製新形狀時要應用的合成操作的型別,其中 type 是一個字串,用於標識十二種合成操作中的哪一種。

剪裁路徑

剪裁路徑就像一個普通的畫布形狀,但它充當一個遮罩,用於隱藏形狀中不需要的部分。下圖對此進行了視覺化。紅色的星形是我們剪裁路徑。所有落在這個路徑之外的部分都不會被繪製到畫布上。

A canvas with a star outlined in red color. The inside of the star is transparent, as portrayed by the grid squares inside the star being clearly visible whereas the grid squares lying outside the star are blurred.

如果我們比較剪裁路徑和上面看到的 globalCompositeOperation 屬性,我們會發現 source-insource-atop 兩種合成模式實現了或多或少相同的效果。兩者之間最重要的區別在於,剪裁路徑實際上永遠不會被繪製到畫布上,並且剪裁路徑永遠不會受到新增新形狀的影響。這使得剪裁路徑成為在受限區域繪製多個形狀的理想選擇。

在關於繪製形狀的章節中,我只提到了 stroke()fill() 方法,但我們還有一個可以與路徑一起使用的第三種方法,稱為 clip()

clip()

將當前正在構建的路徑轉換為當前的剪裁路徑。

您可以使用 clip() 來代替 closePath() 來閉合路徑並將其轉換為剪裁路徑,而不是描邊或填充路徑。

預設情況下,<canvas> 元素有一個剪裁路徑,其大小與畫布本身完全相同。換句話說,沒有發生剪裁。

一個 clip 示例

在這個例子中,我們將使用一個圓形的剪裁路徑來限制一組隨機星星在一個特定區域的繪製。

js
function draw() {
  const ctx = document.getElementById("canvas").getContext("2d");
  ctx.fillRect(0, 0, 150, 150);
  ctx.translate(75, 75);

  // Create a circular clipping path
  ctx.beginPath();
  ctx.arc(0, 0, 60, 0, Math.PI * 2, true);
  ctx.clip();

  // Draw background
  const linGrad = ctx.createLinearGradient(0, -75, 0, 75);
  linGrad.addColorStop(0, "#232256");
  linGrad.addColorStop(1, "#143778");

  ctx.fillStyle = linGrad;
  ctx.fillRect(-75, -75, 150, 150);

  generateStars(ctx);
}

function generateStars(ctx) {
  for (let j = 1; j < 50; j++) {
    ctx.save();
    ctx.fillStyle = "white";
    ctx.translate(
      75 - Math.floor(Math.random() * 150),
      75 - Math.floor(Math.random() * 150),
    );
    drawStar(ctx, Math.floor(Math.random() * 4) + 2);
    ctx.restore();
  }
}

function drawStar(ctx, r) {
  ctx.save();
  ctx.beginPath();
  ctx.moveTo(r, 0);
  for (let i = 0; i < 9; i++) {
    ctx.rotate(Math.PI / 5);
    if (i % 2 === 0) {
      ctx.lineTo((r / 0.525731) * 0.200811, 0);
    } else {
      ctx.lineTo(r, 0);
    }
  }
  ctx.closePath();
  ctx.fill();
  ctx.restore();
}

在程式碼的前幾行,我們繪製一個黑色矩形作為畫布的背景,然後將原點平移到中心。接下來,我們透過繪製一個弧線並呼叫 clip() 來建立圓形的剪裁路徑。剪裁路徑也是畫布儲存狀態的一部分。如果我們想保留原始剪裁路徑,我們可以在建立新路徑之前儲存畫布狀態。

在建立剪裁路徑後繪製的所有內容都只會出現在該路徑內部。您可以清楚地在接下來繪製的線性漸變中看到這一點。之後,我們使用自定義的 drawStar() 函式繪製了 50 顆隨機位置和縮放的星星。同樣,星星只出現在定義的剪裁路徑內部。

反向剪裁路徑

不存在反向剪裁遮罩。但是,我們可以定義一個遮罩,它用一個矩形填充整個畫布,並在您想跳過的部分留下一個孔。在繪製帶有孔洞的形狀時,我們需要以與外部形狀相反的方向繪製孔洞。在下面的示例中,我們在天空中打了一個洞。

矩形沒有繪圖方向,但它的行為就像我們順時針繪製它一樣。預設情況下,弧線命令也是順時針的,但我們可以透過最後一個引數更改其方向。

js
function draw() {
  const canvas = document.getElementById("canvas");
  const ctx = canvas.getContext("2d");
  ctx.translate(75, 75);

  // Clipping path
  ctx.beginPath();
  ctx.rect(-75, -75, 150, 150); // Outer rectangle
  ctx.arc(0, 0, 60, 0, Math.PI * 2, true); // Hole anticlockwise
  ctx.clip();

  // Draw background
  const linGrad = ctx.createLinearGradient(0, -75, 0, 75);
  linGrad.addColorStop(0, "#232256");
  linGrad.addColorStop(1, "#143778");

  ctx.fillStyle = linGrad;
  ctx.fillRect(-75, -75, 150, 150);

  generateStars(ctx);
}