在 CSS 或 javascript 中应用 feColorMatrix SVG 滤镜时的不同结果

Different results when applying feColorMatrix SVG filter in CSS or in javascript

假设我们要在 canvas 元素上应用 SVG 滤镜。根据 this 我们可以像这样在 javascript 中对 CanvasRenderingContext2D 应用 SVG 滤镜,滤镜只会影响调用后绘制的形状:

ctx.filter = "url(#bar)";

我们也可以只应用CSS中的过滤器整体canvas:

#canvas {
  filter: url(#bar);
}

我需要在 javascript 中应用过滤器,因为我只想过滤掉 canvas 的一部分。将 feColorMatrix 应用于某些或所有形状时,结果会有所不同,具体取决于已应用于 JS 中的 2D 上下文或 CSS.

中的整个 canvas 元素的滤镜

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.filter = "url(#bar)";
ctx.fillRect(10,10,100,100);
ctx.fillRect(10,120,100,100);
ctx.fillRect(120,10,100,100);
ctx.fillStyle = 'red';
ctx.beginPath();
ctx.ellipse(170, 170, 50, 50, Math.PI / 4, 0, 2 * Math.PI);
ctx.fill();
#canvas {
  /* remove comment to see difference */
  /* filter: url(#bar); */
}
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <defs>
    <filter id="bar">
      <fegaussianblur in="SourceGraphic" stdDeviation="10" result="blur"></fegaussianblur>
      <fecolormatrix in="blur" mode="matrix" values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 18 -7"></fecolormatrix>
    </filter>
  </defs>
</svg>
<canvas id="canvas" width="400" height="400"></canvas>

当你删除整个应用 SVG 过滤器的评论时 canvas 它提供了这种很棒的粘稠效果,我似乎无法仅用 JS 实现这种效果。我在这里错过了什么,这两种方法不应该给出相同的结果吗?

CSS 滤镜应用于整个 canvas 图像。这与您的 JS 代码根本不同,后者分别在每个矩形上应用过滤器。

以这段代码为例,我在其中绘制了具有一定透明度的矩形。左侧的每个矩形都是一个一个绘制的,而右侧的每个矩形都是在一次绘制操作中绘制的。您可以看到 globalAlpha 根本没有产生相同的结果。

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

ctx.globalAlpha = 0.25;
for(let y = 0; y<150; y+=10) {
  // draws each rectangle one after the other
  ctx.fillRect(0, 0, 50, y);
}
for(let y = 0; y<150; y+=10) {
  ctx.rect(100, 0, 50, y);
}
// draws all the right-side rectangles in one go
ctx.fill();
<canvas></canvas>

好吧,过滤器也会发生同样的事情。
要获得相同的效果,请绘制一次所有矩形,然后使用滤镜在其自身上重新绘制 canvas,以便它应用于整个图像。

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

ctx.fillRect(10,10,100,100);
ctx.fillRect(10,120,100,100);
ctx.fillRect(120,10,100,100);
ctx.fillStyle = 'red';
ctx.beginPath();
ctx.ellipse(170, 170, 50, 50, Math.PI / 4, 0, 2 * Math.PI);
ctx.fill();
ctx.filter = "url(#bar)";
// clears what was there, alternatively we could have used a second canvas
ctx.globalCompositeOperation = "copy";
ctx.drawImage(canvas, 0, 0);
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" style="position:absolute;z-index:-1">
  <defs>
    <filter id="bar">
      <fegaussianblur in="SourceGraphic" stdDeviation="10" result="blur"></fegaussianblur>
      <fecolormatrix in="blur" mode="matrix" values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 18 -7"></fecolormatrix>
    </filter>
  </defs>
</svg>
<canvas id="canvas" width="400" height="400"></canvas>