合成和裁剪

在我们之前的示例中,形状始终是绘制在彼此之上。对于大多数情况来说,这已经足够了,但它限制了复合形状的构建顺序。然而,我们可以通过设置 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);
}