JavaScript 画布中透明框后面的模糊

lqd*_*dev 2 javascript html5-canvas

如何fillStyle = 'rgba(255, 255, 255, 0.2)'在 JavaScript 画布中实现透明框 ( ) 后面的模糊?这是我到目前为止所得到的:

var canvas = document.getElementById('draw');
var c = canvas.getContext('2d');

function main() {
    c.fillStyle = '#222';
    c.fillRect(0, 0, canvas.width, canvas.height);

    c.fillStyle = '#000';
    c.fillRect(32, 32, 64, 64);
    c.fillStyle = 'rgba(255, 255, 255, 0.2)';
    c.filter = 'blur(5px)';
    c.fillRect(16, 16, 128, 24);
}
Run Code Online (Sandbox Code Playgroud)

但所发生的情况是,矩形本身被模糊了,而不是模糊了矩形后面的背景,这很明显。

模糊的矩形

在最终的脚本中,我可能会使用路径而不是矩形。

Kai*_*ido 7

Context2D 滤镜将仅应用于您的新绘图,因此为了模糊背景,您实际上必须重新绘制想要模糊的背景部分。

幸运的是,canvas本身可以绘制图像。

var blurredRect = {
  x: 80,
  y: 80,
  height: 200,
  width: 200,
  spread: 10
};
var ctx = canvas.getContext('2d');

var img = new Image();
img.onload = draw;
img.src = 'https://upload.wikimedia.org/wikipedia/commons/5/55/John_William_Waterhouse_A_Mermaid.jpg';

function draw() {
  canvas.width = img.width / 2;
  canvas.height = img.height / 2;
  // first pass draw everything
  ctx.drawImage(img, 0,0, canvas.width, canvas.height); 
  // next drawings will be blurred
  ctx.filter = 'blur('+ blurredRect.spread +'px)';
  // draw the canvas over itself, cropping to our required rect
  ctx.drawImage(canvas,
    blurredRect.x, blurredRect.y, blurredRect.width, blurredRect.height,
    blurredRect.x, blurredRect.y, blurredRect.width, blurredRect.height
  );
  // draw the coloring (white-ish) layer, without blur
  ctx.filter = 'none'; // remove filter
  ctx.fillStyle = 'rgba(255,255,255,0.2)';
  ctx.fillRect(blurredRect.x, blurredRect.y, blurredRect.width, blurredRect.height);
}
Run Code Online (Sandbox Code Playgroud)
<canvas id="canvas"></canvas>
Run Code Online (Sandbox Code Playgroud)

但是,canvas模糊滤镜与 CSS 模糊滤镜有点不同,因为它会使扩散停留在绘制区域内。这意味着在我们的例子中,我们的矩形周围有一个 5px 的边框,它比中心的模糊程度要低。

为了解决这个问题,我们可以以不同的顺序处理整个事情并使用globalCompositeOperation属性*:

var blurredRect = {
  x: 80,
  y: 80,
  height: 200,
  width: 200,
  spread: 10
};
var ctx = canvas.getContext('2d');

var img = new Image();
img.onload = draw;
img.src = 'https://upload.wikimedia.org/wikipedia/commons/5/55/John_William_Waterhouse_A_Mermaid.jpg';

function draw() {
  var spread = blurredRect.spread,
    ratio = 0.5,
    // make our blurred rect spreads
    x = blurredRect.x - spread,
    y = blurredRect.y - spread,
    w = blurredRect.width + (spread * 2),
    h = blurredRect.height + (spread * 2);
    
  canvas.width = img.width * ratio;
  canvas.height = img.height * ratio;
  
  // this time we will first draw the blurred rect
  ctx.filter = 'blur('+ spread +'px)';
  // this time we draw from the img directly
  ctx.drawImage(img,
    x / ratio, y / ratio, w / ratio, h / ratio,
    x, y, w, h
  );
  
  // now we will want to crop the resulting blurred image to the required one, so we get a clear-cut

  ctx.filter = 'none'; // remove filter
  // with this mode, previous drawings will be kept where new drawings are made
  ctx.globalCompositeOperation = 'destination-in';
  ctx.fillStyle = '#000'; // make it opaque
  ctx.rect(blurredRect.x, blurredRect.y, blurredRect.width, blurredRect.height);
  ctx.fill(); // clear-cut done
  // reuse our rect to make the white-ish overlay
  ctx.fillStyle = 'rgba(255,255,255,0.2)';
  // reset gCO to its default
  ctx.globalCompositeOperation = 'source-over';
  ctx.fill();
  
  // now we will draw behind the our blurred rect
  ctx.globalCompositeOperation = 'destination-over';
  ctx.drawImage(img, 0,0, canvas.width, canvas.height); 

  // reset to defaults
  ctx.globalCompositeOperation = 'source-over';
}
Run Code Online (Sandbox Code Playgroud)
<canvas id="canvas"></canvas>
Run Code Online (Sandbox Code Playgroud)

但这种方法要求我们将整个背景作为可绘制的东西进行访问,在上面的示例中,这只是一个图像,但在现实生活中,这可能意味着您必须在第二个屏幕外画布上执行此操作。

var blurredRect = {
  x: 80,
  y: 80,
  height: 200,
  width: 200,
  spread: 2
};
var ctx = canvas.getContext('2d');
// create an off-screen canvas
var bCanvas = canvas.cloneNode();
var bCtx = bCanvas.getContext('2d');

var img = new Image();
img.onload = draw;
img.src = 'https://upload.wikimedia.org/wikipedia/commons/5/55/John_William_Waterhouse_A_Mermaid.jpg';

function draw() {
  var spread = blurredRect.spread;

  canvas.width = bCanvas.width = img.width / 2;
  canvas.height = bCanvas.height = img.height / 2;

  // now we have a composed background
  ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  ctx.font = '40px Impact';
  ctx.fillStyle = 'white';
  ctx.fillText('..SO BLUR ME..', 120, 282);
  
  // make our clear-cut on the offscreen canvas
  bCtx.filter = 'blur(' + spread +'px)';
  bCtx.drawImage(canvas,
    blurredRect.x - spread, blurredRect.y - spread, blurredRect.width + spread * 2, blurredRect.height + spread * 2,
    blurredRect.x - spread, blurredRect.y - spread, blurredRect.width + spread * 2, blurredRect.height + spread * 2
  );
  // clear-cut
  bCtx.filter = 'none';
  bCtx.globalCompositeOperation = 'destination-in';
  bCtx.beginPath();
  bCtx.rect(blurredRect.x, blurredRect.y, blurredRect.width, blurredRect.height);
  bCtx.fillStyle = '#000';
  bCtx.fill();
  // white-ish layer
  bCtx.globalCompositeOperation = 'source-over';
  bCtx.fillStyle = 'rgba(255,255,255,0.2)';
  bCtx.fillRect(blurredRect.x, blurredRect.y, blurredRect.width, blurredRect.height);

  // now just redraw on the visible canvas
  ctx.drawImage(bCanvas, 0,0);
 
}
Run Code Online (Sandbox Code Playgroud)
<canvas id="canvas"></canvas>
Run Code Online (Sandbox Code Playgroud)


*有人可能会说,我们可以使用屏幕外画布和 gCO 来代替ctx.clip(),但既然你说它可能是比矩形更复杂的路径,我不会建议这样做。事实上,虽然它需要更少的代码,并且可能使用更少的内存,但剪切对于抗锯齿来说是很糟糕的,而且由于你正在进行模糊处理,所以看起来会很丑陋。