CanvasRenderingContext2D: globalCompositeOperation 属性

Baseline 已广泛支持

此特性已相当成熟,可在许多设备和浏览器版本上使用。自 ⁨2015 年 7 月⁩以来,各浏览器均已提供此特性。

Canvas 2D API 的 CanvasRenderingContext2D.globalCompositeOperation 属性用于设置绘制新形状时应用的复合操作类型。

另请参阅 Canvas 教程中的 复合和裁剪

一个字符串,用于标识要使用的复合或混合模式操作。它可以是以下任何值:

"source-over"

这是默认设置,会在现有画布内容之上绘制新形状。

"source-in"

新形状仅在新形状和目标画布重叠的区域绘制。其他所有内容都将变为透明。

"source-out"

新形状在新形状不与现有画布内容重叠的区域绘制。

"source-atop"

新形状仅在新形状与现有画布内容重叠的区域绘制。

"destination-over"

新形状会在现有画布内容之后绘制。

"destination-in"

现有画布内容会在新形状和现有画布内容重叠的区域保留。其他所有内容都将变为透明。

"destination-out"

现有内容会在不与新形状重叠的区域保留。

"destination-atop"

现有画布仅在新形状重叠的区域保留。新形状会在画布内容之后绘制。

"lighter"

在两个形状重叠的区域,颜色通过将颜色值相加来确定。

"copy"

仅显示新形状。

"xor"

在两个形状重叠的区域,形状会变为透明;在其他所有区域正常绘制。

"multiply"

顶层像素与底层对应像素相乘。结果是更暗的图像。

"screen"

像素反转、相乘,然后再次反转。结果是更亮的图像(与 multiply 相反)。

"overlay"

multiplyscreen 的组合。基础图层的暗部变得更暗,亮部变得更亮。

"darken"

保留两个图层中最暗的像素。

"lighten"

保留两个图层中最亮的像素。

"color-dodge"

用反转的顶层图层除以底层图层。

"color-burn"

用顶层图层除以反转的底层图层,然后反转结果。

"hard-light"

类似于 overlay,是 multiplyscreen 的组合——但顶层和底层被交换了。

"soft-light"

hard-light 的柔和版本。纯黑或纯白不会产生纯黑或纯白。

"difference"

从顶层图层减去底层图层(或反之),始终得到一个正值。

"exclusion"

类似于 difference,但对比度较低。

"hue"

保留底层图层的亮度(luma)和色度(chroma),同时采用顶层图层的色相(hue)。

"saturation"

保留底层图层的亮度(luma)和色相(hue),同时采用顶层图层的色度(chroma)。

"color"

保留底层图层的亮度(luma),同时采用顶层图层的色相(hue)和色度(chroma)。

"luminosity"

保留底层图层的色相(hue)和色度(chroma),同时采用顶层图层的亮度(luma)。

示例

更改复合操作

此示例使用 globalCompositeOperation 属性绘制两个矩形,它们在重叠区域会相互排除。

HTML

html
<canvas id="canvas"></canvas>

JavaScript

js
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

ctx.globalCompositeOperation = "xor";

ctx.fillStyle = "blue";
ctx.fillRect(10, 10, 100, 100);

ctx.fillStyle = "red";
ctx.fillRect(50, 50, 100, 100);

结果

所有值的演示

全局值

此代码设置了程序其余部分使用的全局值。

js
const canvas1 = document.createElement("canvas");
const canvas2 = document.createElement("canvas");
const gco = [
  "source-over",
  "source-in",
  "source-out",
  "source-atop",
  "destination-over",
  "destination-in",
  "destination-out",
  "destination-atop",
  "lighter",
  "copy",
  "xor",
  "multiply",
  "screen",
  "overlay",
  "darken",
  "lighten",
  "color-dodge",
  "color-burn",
  "hard-light",
  "soft-light",
  "difference",
  "exclusion",
  "hue",
  "saturation",
  "color",
  "luminosity",
].reverse();
const gcoText = [
  "This is the default setting and draws new shapes on top of the existing canvas content.",
  "The new shape is drawn only where both the new shape and the destination canvas overlap. Everything else is made transparent.",
  "The new shape is drawn where it doesn't overlap the existing canvas content.",
  "The new shape is only drawn where it overlaps the existing canvas content.",
  "New shapes are drawn behind the existing canvas content.",
  "The existing canvas content is kept where both the new shape and existing canvas content overlap. Everything else is made transparent.",
  "The existing content is kept where it doesn't overlap the new shape.",
  "The existing canvas is only kept where it overlaps the new shape. The new shape is drawn behind the canvas content.",
  "Where both shapes overlap the color is determined by adding color values.",
  "Only the new shape is shown.",
  "Shapes are made transparent where both overlap and drawn normal everywhere else.",
  "The pixels of the top layer are multiplied with the corresponding pixel of the bottom layer. A darker picture is the result.",
  "The pixels are inverted, multiplied, and inverted again. A lighter picture is the result (opposite of multiply)",
  "A combination of multiply and screen. Dark parts on the base layer become darker, and light parts become lighter.",
  "Retains the darkest pixels of both layers.",
  "Retains the lightest pixels of both layers.",
  "Divides the bottom layer by the inverted top layer.",
  "Divides the inverted bottom layer by the top layer, and then inverts the result.",
  "A combination of multiply and screen like overlay, but with top and bottom layer swapped.",
  "A softer version of hard-light. Pure black or white does not result in pure black or white.",
  "Subtracts the bottom layer from the top layer or the other way round to always get a positive value.",
  "Like difference, but with lower contrast.",
  "Preserves the luma and chroma of the bottom layer, while adopting the hue of the top layer.",
  "Preserves the luma and hue of the bottom layer, while adopting the chroma of the top layer.",
  "Preserves the luma of the bottom layer, while adopting the hue and chroma of the top layer.",
  "Preserves the hue and chroma of the bottom layer, while adopting the luma of the top layer.",
].reverse();
const width = 320;
const height = 340;

// lum in sRGB
const lum = {
  r: 0.33,
  g: 0.33,
  b: 0.33,
};
// resize canvas
canvas1.width = width;
canvas1.height = height;
canvas2.width = width;
canvas2.height = height;

主程序

此代码 runComposite() 负责大部分工作,并依赖一些辅助函数来完成困难的部分。

js
function createCanvas(op) {
  const canvas = document.createElement("canvas");
  canvas.style.background = `url(${JSON.stringify(op.data)})`;
  canvas.style.border = "1px solid black";
  canvas.style.margin = "5px";
  canvas.width = width / 2;
  canvas.height = height / 2;
  return canvas;
}

function runComposite(op) {
  const dl = document.createElement("dl");
  document.body.appendChild(dl);
  while (gco.length) {
    const pop = gco.pop();
    const dt = document.createElement("dt");
    dt.textContent = pop;
    dl.appendChild(dt);
    const dd = document.createElement("dd");
    const p = document.createElement("p");
    p.textContent = gcoText.pop();
    dd.appendChild(p);

    const canvasToDrawOn = createCanvas(op);
    const canvasToDrawFrom = createCanvas(op);
    const canvasToDrawResult = createCanvas(op);

    let ctx = canvasToDrawResult.getContext("2d");
    ctx.clearRect(0, 0, width, height);
    ctx.save();
    ctx.drawImage(canvas1, 0, 0, width / 2, height / 2);
    ctx.globalCompositeOperation = pop;
    ctx.drawImage(canvas2, 0, 0, width / 2, height / 2);
    ctx.globalCompositeOperation = "source-over";
    ctx.fillStyle = "rgb(0 0 0 / 80%)";
    ctx.fillRect(0, height / 2 - 20, width / 2, 20);
    ctx.fillStyle = "white";
    ctx.font = "14px arial";
    ctx.fillText(pop, 5, height / 2 - 5);
    ctx.restore();

    ctx = canvasToDrawOn.getContext("2d");
    ctx.clearRect(0, 0, width, height);
    ctx.save();
    ctx.drawImage(canvas1, 0, 0, width / 2, height / 2);
    ctx.fillStyle = "rgb(0 0 0 / 80%)";
    ctx.fillRect(0, height / 2 - 20, width / 2, 20);
    ctx.fillStyle = "white";
    ctx.font = "14px arial";
    ctx.fillText("existing content", 5, height / 2 - 5);
    ctx.restore();

    ctx = canvasToDrawFrom.getContext("2d");
    ctx.clearRect(0, 0, width, height);
    ctx.save();
    ctx.drawImage(canvas2, 0, 0, width / 2, height / 2);
    ctx.fillStyle = "rgb(0 0 0 / 80%)";
    ctx.fillRect(0, height / 2 - 20, width / 2, 20);
    ctx.fillStyle = "white";
    ctx.font = "14px arial";
    ctx.fillText("new content", 5, height / 2 - 5);
    ctx.restore();

    dd.appendChild(canvasToDrawOn);
    dd.appendChild(canvasToDrawFrom);
    dd.appendChild(canvasToDrawResult);

    dl.appendChild(dd);
  }
}

辅助函数

程序依赖于一些辅助函数。

js
function lightMix() {
  const ctx = canvas2.getContext("2d");
  ctx.save();
  ctx.globalCompositeOperation = "lighter";
  ctx.beginPath();
  ctx.fillStyle = "red";
  ctx.arc(100, 200, 100, Math.PI * 2, 0, false);
  ctx.fill();
  ctx.beginPath();
  ctx.fillStyle = "blue";
  ctx.arc(220, 200, 100, Math.PI * 2, 0, false);
  ctx.fill();
  ctx.beginPath();
  ctx.fillStyle = "lime";
  ctx.arc(160, 100, 100, Math.PI * 2, 0, false);
  ctx.fill();
  ctx.restore();
  ctx.beginPath();
  ctx.fillStyle = "red";
  ctx.fillRect(0, 0, 30, 30);
  ctx.fill();
}
js
function colorSphere() {
  const ctx = canvas1.getContext("2d");
  const width = 360;
  const halfWidth = width / 2;
  const rotate = (1 / 360) * Math.PI * 2; // per degree
  const offset = 0; // scrollbar offset
  const oLeft = -20;
  const oTop = -20;
  for (let n = 0; n <= 359; n++) {
    const gradient = ctx.createLinearGradient(
      oLeft + halfWidth,
      oTop,
      oLeft + halfWidth,
      oTop + halfWidth,
    );
    const color = Color.HSV_RGB({ H: (n + 300) % 360, S: 100, V: 100 });
    gradient.addColorStop(0, "transparent");
    gradient.addColorStop(0.7, `rgb(${color.R} ${color.G} ${color.B})`);
    gradient.addColorStop(1, "white");
    ctx.beginPath();
    ctx.moveTo(oLeft + halfWidth, oTop);
    ctx.lineTo(oLeft + halfWidth, oTop + halfWidth);
    ctx.lineTo(oLeft + halfWidth + 6, oTop);
    ctx.fillStyle = gradient;
    ctx.fill();
    ctx.translate(oLeft + halfWidth, oTop + halfWidth);
    ctx.rotate(rotate);
    ctx.translate(-(oLeft + halfWidth), -(oTop + halfWidth));
  }
  ctx.beginPath();
  ctx.fillStyle = "blue";
  ctx.fillRect(15, 15, 30, 30);
  ctx.fill();
  return ctx.canvas;
}
js
// HSV (1978) = H: Hue / S: Saturation / V: Value
Color = {};
Color.HSV_RGB = (o) => {
  const S = o.S / 100;
  let H = o.H / 360,
    V = o.V / 100;
  let R, G;
  let A, B, C, D;
  if (S === 0) {
    R = G = B = Math.round(V * 255);
  } else {
    if (H >= 1) H = 0;
    H *= 6;
    D = H - Math.floor(H);
    A = Math.round(255 * V * (1 - S));
    B = Math.round(255 * V * (1 - S * D));
    C = Math.round(255 * V * (1 - S * (1 - D)));
    V = Math.round(255 * V);
    switch (Math.floor(H)) {
      case 0:
        R = V;
        G = C;
        B = A;
        break;
      case 1:
        R = B;
        G = V;
        B = A;
        break;
      case 2:
        R = A;
        G = V;
        B = C;
        break;
      case 3:
        R = A;
        G = B;
        B = V;
        break;
      case 4:
        R = C;
        G = A;
        B = V;
        break;
      case 5:
        R = V;
        G = A;
        // B remains unchanged
        break;
    }
  }
  return { R, G, B };
};

function createInterlace(size, color1, color2) {
  const proto = document.createElement("canvas").getContext("2d");
  proto.canvas.width = size * 2;
  proto.canvas.height = size * 2;
  proto.fillStyle = color1; // top-left
  proto.fillRect(0, 0, size, size);
  proto.fillStyle = color2; // top-right
  proto.fillRect(size, 0, size, size);
  proto.fillStyle = color2; // bottom-left
  proto.fillRect(0, size, size, size);
  proto.fillStyle = color1; // bottom-right
  proto.fillRect(size, size, size, size);
  const pattern = proto.createPattern(proto.canvas, "repeat");
  pattern.data = proto.canvas.toDataURL();
  return pattern;
}

const op_8x8 = createInterlace(8, "white", "#eeeeee");

开始运行

最后,我们调用函数来启动一切。

js
lightMix();
colorSphere();
runComposite(op_8x8);

结果

规范

规范
HTML
# dom-context-2d-globalcompositeoperation-dev

浏览器兼容性

另见