I am trying to figure out CanvasRenderingContext2D.globalCompositeOperation = "destination-out
in order to create a "Fog of War" effect.
Given this example
https://jsfiddle.net/wobzpjLL/
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
// a green background box
ctx.fillStyle = "green";
ctx.fillRect(0,0,300,150);
// a line
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(300, 150);
ctx.stroke();
// Fog of war
ctx.fillStyle = "black";
ctx.fillRect(0,0,300,150);
// Here I want two circles that shows the background (green & line)
// text
ctx.fillStyle = "red";
ctx.font = "30px Arial";
ctx.fillText("Hello World",10,50);
How do I create two circles that excludes the black overlay, so that the user can see the green background and the line, within those two circles, black everywhere else (hence creating a fog of war effect)?
The easiest way to achieve the masking is by creating a offscreen canvas and render the mask onto that. Then when animating you render the world and then apply the mask with "destination-in"
. You can use the canvas CSS background as the fog (as done in demo) or you can render the fog over the top with "destination-over"
also shown in demo using the text.
Personally and for performance (if code is needing every break it can get) I would avoid globalCompositeOperation
and render the fog over the world by creating the inverse of the mask in the demo. This adds only the one extra render call and no need to change composite operations
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
function createCanvas(){
var c = document.createElement("canvas");
c.width = 300;
c.height = 150;
c.ctx = c.getContext("2d");
return c;
}
// draw game world on offscreen canvas
function drawBackground(ctx){
ctx.fillStyle = "green";
ctx.fillRect(0,0,300,150);
// a line
ctx.beginPath();
ctx.lineWidth = 3;
ctx.strokeStyle = "#08A";
ctx.lineTo(0, 0);
ctx.lineTo(300, 150);
ctx.stroke();
}
// create and render world
var bGround = createCanvas();
drawBackground(bGround.ctx)
// create mask
var grad = ctx.createRadialGradient(0,0,60,0,0,0);
grad.addColorStop(0,"rgba(0,0,0,0.0)");
grad.addColorStop(0.2,"rgba(0,0,0,1)");
grad.addColorStop(1,"rgba(0,0,0,1)");
var mask = createCanvas();
mask.ctx.setTransform(1,0,0,1,75,75);
mask.ctx.fillStyle = grad;
mask.ctx.beginPath();
mask.ctx.arc(0,0,60,0,Math.PI*2);
mask.ctx.fill();
mask.ctx.setTransform(1,0,0,1,225,75);
mask.ctx.beginPath();
mask.ctx.arc(0,0,60,0,Math.PI*2);
mask.ctx.fill();
document.body.appendChild(mask);
// draws the fog
function drawFog(ctx,x){
ctx.globalCompositeOperation = "destination-in";
ctx.drawImage(mask, x, 0);
ctx.globalCompositeOperation = "source-over";
}
ctx.fillStyle = "red";
ctx.font = "30px Arial";
function loop(time){
ctx.drawImage(bGround,0,0);
drawFog(ctx,Math.sin(time / 1000) *20);
ctx.globalCompositeOperation = "destination-over";
ctx.fillText("Hello World",10,50);
ctx.globalCompositeOperation = "source-over";
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
canvas {
border:1px solid #d3d3d3;
}
<canvas id="myCanvas" width="300" height="150" style="background : black;" ></canvas>
<div>Pre rendered offscreen mask shown below for display only.</div>