Reputation: 150
I can't see that this had been posted already, so here goes.
Let's say i draw 2 squares on the canvas.
var c = document.getElementById('test'), ctx = c.getContext('2d');
ctx.fillStyle = "rgba(0,0,255,0.5)";
ctx.beginPath();
ctx.moveTo(25, 0);
ctx.lineTo(50, 50);
ctx.lineTo(0, 50);
ctx.lineTo(25, 0);
ctx.fill();
ctx.fillStyle = "rgba(255,0,0,0.5)";
ctx.beginPath();
ctx.moveTo(50, 0);
ctx.lineTo(75, 50);
ctx.lineTo(25, 50);
ctx.lineTo(50, 0);
ctx.fill();
This produces this image:
If i change globalAlpha to 0.5, i get this:
However, i want to produce this:
As in, all pixels are transparent and any images under it will appear, but the pixels created by the red triangle will overwrite the existing blue triangle where it is drawn.
And ctx.globalComposisteOperation doesn't seem to help in this instance due to it also factoring the transparency and the fact i want to keep both squares.
Is there any way to do this with current methods?
Upvotes: 1
Views: 2585
Reputation: 54026
Do it the photoshop way and create layers to do the work for you. Creating a layer (second canvas) is no more trouble than loading an image. You can create dozens and have no problem and it makes this type of work easy.
First create a second canvas (layer)
// canvas is original canvas
var layer = document.createElement("canvas");
layer.width = canvas.width; // same size as original
layer.height = canvas.height;
var ctx1 = layer.getContext("2d");
Then draw your triangles on the second canvas with alpha = 1;
var tri = (x,c)=>{
ctx1.fillStyle = c;
ctx1.beginPath();
ctx1.moveTo(25 + x, 0);
ctx1.lineTo(50 + x, 50);
ctx1.lineTo(0 + x, 50);
ctx1.closePath();
ctx1.fill();
}
tri(0,"#00f");
tri(25,"#f00");
Then just draw that layer on top of the canvas you are working on with the alpha value you want.
ctx.globalAlpha = 0.5;
ctx.drawImage(layer,0,0);
If you don't need the extra layer delete the canvas and context by dereferencing them.
ctx1 = undefined;
layer = undefined;
Or you can keep the layer , and make another layer for the background and mix them in real time to get the FX just right
//
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
var layer = document.createElement("canvas");
layer.width = canvas.width; // same size as original
layer.height = canvas.height;
var ctx1 = layer.getContext("2d");
var tri = (x,c)=>{
ctx1.fillStyle = c;
ctx1.beginPath();
ctx1.moveTo(25 + x, 0);
ctx1.lineTo(50 + x, 50);
ctx1.lineTo(0 + x, 50);
ctx1.fill();
}
tri(0,"#00f");
tri(25,"#0f0");
tri(50,"#f00");
ctx.globalAlpha = 0.5;
ctx.drawImage(layer,0,0);
layer = ctx1 = undefined;
Upvotes: 1
Reputation: 136608
@markE is right, use compositing instead of clipping (really heavy).
However, his solution will only work if you've drawn everything with an rgba color. Maybe I read your question wrongly, but if you're going from an opaque shape and want to make it transparent, then you should rather use the copy
gCO and the globalAlpha
property.
This will have less performance impact since drawImage
is faster than fill
, and will allow you to perform a fade-out effect ; but it really depends on your needs.
var ctx = c.getContext('2d');
// initial blue
ctx.fillStyle = "#0000FF";
ctx.beginPath();
ctx.moveTo(25, 0);
ctx.lineTo(50, 50);
ctx.lineTo(0, 50);
ctx.lineTo(25, 0);
ctx.fill();
setTimeout(function drawRed() {
ctx.fillStyle = "#FF0000";
ctx.beginPath();
ctx.moveTo(50, 0);
ctx.lineTo(75, 50);
ctx.lineTo(25, 50);
ctx.lineTo(50, 0);
ctx.fill();
}, 500);
btn.onclick = function makeItTransparent() {
// if we weere to make this into an animation, we would set it only once
ctx.globalCompositeOperation = 'copy';
ctx.globalAlpha = .8;
// draw the canvas on itself
ctx.drawImage(c, 0, 0);
// once again, in an animation we won't reset this to default
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = 'source-over';
};
/* checkboard background */
canvas {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAGElEQVQYlWNgYGBowIKxgqGgcJA5h3yFAOI3GQFqqi5ZAAAAAElFTkSuQmCC");
}
<canvas id="c"></canvas>
<button id="btn">make it transparent</button>
Upvotes: 0
Reputation: 105015
Use Compositing to clear the red triangle before drawing it.
Using compositing is slightly better than clipping because you don't have to clear the clip. Clearing a clip requires saving the entire context state and then restoring that context state -- many properties involved. Compositing just requires changing 1 property forth and back.
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// fill the blue rect
ctx.fillStyle = "rgba(0,0,255,0.5)";
ctx.beginPath();
ctx.moveTo(25, 0);
ctx.lineTo(50, 50);
ctx.lineTo(0, 50);
ctx.lineTo(25, 0);
ctx.fill();
// define the red rect path
ctx.beginPath();
ctx.moveTo(50, 0);
ctx.lineTo(75, 50);
ctx.lineTo(25, 50);
ctx.lineTo(50, 0);
// clear the red rect path using compositing
ctx.globalCompositeOperation='destination-out';
ctx.fillStyle='black';
ctx.fill();
ctx.globalCompositeOperation='source-over';
// fill the red rect
ctx.fillStyle = "rgba(255,0,0,0.5)";
ctx.fill();
body{ background-color:white; }
#canvas{border:1px solid red; }
<canvas id="canvas" width=512 height=512></canvas>
Upvotes: 2
Reputation: 150
var c = document.getElementById('test'), ctx = c.getContext('2d');
ctx.save();
ctx.fillStyle = "rgba(0,0,255,0.5)";
ctx.beginPath();
ctx.moveTo(25, 0);
ctx.lineTo(50, 50);
ctx.lineTo(0, 50);
ctx.lineTo(25, 0);
ctx.fill();
ctx.fillStyle = "rgba(255,0,0,0.5)";
ctx.beginPath();
ctx.moveTo(50, 0);
ctx.lineTo(75, 50);
ctx.lineTo(25, 50);
ctx.lineTo(50, 0);
ctx.clip();
ctx.clearRect(0, 0, 100, 100);
ctx.fill();
ctx.restore();
Just use clip() to take the current path, clear everything in there with clearRect, then draw the path as normal.
Upvotes: 0
Reputation: 40463
I would honestly suggest just changing the colors and avoid using an alpha.
var c = document.getElementById('game'),
ctx = c.getContext('2d');
ctx.fillStyle = "#8080FF";
ctx.fillRect(0, 0, 50, 50);
ctx.fillStyle = "#ff8080";
ctx.fillRect(25, 25, 50, 50);
<canvas id="game" width="320" height="240"></canvas>
Upvotes: 0
Reputation: 32511
You can clear the area for the second rectangle before drawing it.
var c = document.getElementById('game'),
ctx = c.getContext('2d');
ctx.globalAlpha = 0.5;
ctx.fillStyle = "blue";
ctx.fillRect(0, 0, 50, 50);
// Clear the extra bit of the blue rectangle
ctx.clearRect(25, 25, 25, 25);
ctx.fillStyle = "red";
ctx.fillRect(25, 25, 50, 50);
<canvas id="game" width="320" height="240"></canvas>
Upvotes: 0