Blur behind transparent box in JavaScript canvas

泪湿孤枕 提交于 2020-01-06 08:32:16

问题


How can I achieve a blur behind a transparent box (fillStyle = 'rgba(255, 255, 255, 0.2)') in JavaScript canvas? Here's what I've got so far:

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);
}

But what happens, is instead of blurring the background behind the rectangle, is the rectangle itself is blurred, kind of obviously.

In the final script, I will probably use paths instead of rects.


回答1:


Context2D filters will be applied only on your new drawings, so to also blur the background, you would actually have to redraw the part of the background you want to be blurred.

Fortunately, canvas can drawImage itself.

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);
}
<canvas id="canvas"></canvas>

But, canvas blur filter is a bit different than CSS one in that it will make the spreading stay inside the drawn area. This means that in our case, we have a 5px border around our rectangle that is less blurred than the center.

To workaround, we can take the whole thing in a different order and play with globalCompositeOperation property*:

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';
}
<canvas id="canvas"></canvas>

But this approach requires that we keep access to the whole background as a drawable thing, in the example above that was just an image, but in real life, this might mean you'd have to do this operation on a second offscreen canvas.

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);
 
}
<canvas id="canvas"></canvas>

*One may say that instead of an offscreen canvas and gCO we could have used ctx.clip(), but since you said it might a more complex Path than a rect, I will not advise to do so. Indeed, while it would require less code, and maybe use less memory, clipping is just bad with antialiasing, and since you are doing blurring, that will just look plain ugly.



来源:https://stackoverflow.com/questions/50687334/blur-behind-transparent-box-in-javascript-canvas

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!