合成和裁剪

在我们之前所有 示例中,形状始终一个叠加在另一个上面绘制。这对大多数情况来说已经足够了,但它限制了组合形状的构建顺序。但是,我们可以通过设置 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 = "#fff";
    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");
  if (canvas.getContext) {
    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);
  }
}