Reputation: 11000
Sorry I am new to Canvas and dont know how to google this out. Problem is that I cant draw on mask if previous layer (night sky) is present.
Here are the two snippets:
const canvas = document.querySelector('#board canvas');
const ctx = canvas.getContext('2d');
const { width: w, height: h } = canvas;
// first layer
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, w, h);
ctx.fillStyle = '#555';
let x, y, radius;
for (let i = 0; i < 550; i++) {
x = Math.random() * w;
y = Math.random() * h;
radius = Math.random() * 3;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2, false);
ctx.fill();
}
// destination
ctx.font = 'bold 70pt monospace';
ctx.fillStyle = 'black';
ctx.fillText('FOO', 10, 60);
ctx.fillText('BAR', 10, 118);
ctx.fill();
// source
ctx.globalCompositeOperation = 'source-atop';
for (let i = 0; i < 6; i++) {
ctx.fillStyle = `hsl(${i * (250 / 6)}, 90%, 55%)`;
ctx.fillRect(0, i * 20, 200, 20);
}
<div id="board">
<canvas width="640" height="480"></canvas>
</div>
EXPECTED RESULT (but with the first layer - night sky):
const canvas = document.querySelector('#board canvas');
const ctx = canvas.getContext('2d');
const { width: w, height: h } = canvas;
// destination
ctx.font = 'bold 70pt monospace';
ctx.fillStyle = 'black';
ctx.fillText('FOO', 10, 60);
ctx.fillText('BAR', 10, 118);
ctx.fill();
// source
ctx.globalCompositeOperation = 'source-atop';
for (let i = 0; i < 6; i++) {
ctx.fillStyle = `hsl(${i * (250 / 6)}, 90%, 55%)`;
ctx.fillRect(0, i * 20, 200, 20);
}
<div id="board">
<canvas width="640" height="480"></canvas>
</div>
Upvotes: 0
Views: 453
Reputation: 137006
source-atop
mode will draw only where there were existing pixels (i.e only where alpha > 0).This means that source-atop will not produce anything on your fully opaque image.
Once you understand these points, it's clear that you need to make your compositing alone.
It could be e.g on a different off-screen canvas that you would then draw back on the main canvas with ctx.drawImage(canvas, x, y)
.
const canvas = document.querySelector('#board canvas');
const ctx = canvas.getContext('2d');
const {
width: w,
height: h
} = canvas;
// background
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, w, h);
ctx.fillStyle = '#555';
let x, y, radius;
for (let i = 0; i < 550; i++) {
x = Math.random() * w;
y = Math.random() * h;
radius = Math.random() * 3;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2, false);
ctx.fill();
}
// text compositing on an off-screen context
const ctx2 = Object.assign(document.createElement('canvas'), {
width: 200,
height: 120
}).getContext('2d');
// text
ctx2.font = 'bold 70pt monospace';
ctx2.fillStyle = 'black';
ctx2.fillText('FOO', 10, 60);
ctx2.fillText('BAR', 10, 118);
ctx2.globalCompositeOperation = 'source-atop';
// rainbow
for (let i = 0; i < 6; i++) {
ctx2.fillStyle = `hsl(${i * (250 / 6)}, 90%, 55%)`;
ctx2.fillRect(0, i * 20, 200, 20);
}
// now draw our off-screen canvas on the main one
ctx.drawImage(ctx2.canvas, 0, 0);
<div id="board">
<canvas width="640" height="480"></canvas>
</div>
Or, since this is the only compositing in your composition, you can also do it all on the same, but use an other compositing mode: destination-over
.
This mode will draw behind the existing content, this means that you will have to actually draw your background after you made the compositing.
const canvas = document.querySelector('#board canvas');
const ctx = canvas.getContext('2d');
const {
width: w,
height: h
} = canvas;
//
// text compositing on a clear context
drawText();
// will draw only where the text has been drawn
ctx.globalCompositeOperation = 'source-atop';
drawRainbow();
// from here we will draw behind
ctx.globalCompositeOperation = 'destination-over';
// so we need to first draw the stars, otherwise they'll be behind
drawStars();
//And finally the sky black background
drawSky();
//... reset
ctx.globalCompositeOperation = 'source-over';
function drawSky() {
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, w, h);
}
function drawStars() {
ctx.fillStyle = '#555';
let x, y, radius;
for (let i = 0; i < 550; i++) {
x = Math.random() * w;
y = Math.random() * h;
radius = Math.random() * 3;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2, false);
ctx.fill();
}
}
function drawText() {
ctx.font = 'bold 70pt monospace';
ctx.fillStyle = 'black';
ctx.fillText('FOO', 10, 60);
ctx.fillText('BAR', 10, 118);
}
function drawRainbow() {
for (let i = 0; i < 6; i++) {
ctx.fillStyle = `hsl(${i * (250 / 6)}, 90%, 55%)`;
ctx.fillRect(0, i * 20, 200, 20);
}
}
<div id="board">
<canvas width="640" height="480"></canvas>
</div>
Upvotes: 2