Skip to content Skip to sidebar Skip to footer

Blur Behind Transparent Box In Javascript Canvas

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('

Solution 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 = newImage();
img.onload = draw;
img.src = 'https://upload.wikimedia.org/wikipedia/commons/5/55/John_William_Waterhouse_A_Mermaid.jpg';

functiondraw() {
  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);
}
<canvasid="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 = newImage();
img.onload = draw;
img.src = 'https://upload.wikimedia.org/wikipedia/commons/5/55/John_William_Waterhouse_A_Mermaid.jpg';

functiondraw() {
  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';
}
<canvasid="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 canvasvar bCanvas = canvas.cloneNode();
var bCtx = bCanvas.getContext('2d');

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

functiondraw() {
  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);
 
}
<canvasid="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.

Post a Comment for "Blur Behind Transparent Box In Javascript Canvas"