Why does the below canvas have a black background? Because of it, the text behind the canvas is not visible. I need it to be transparent to show the elements behind it and the background image of the body.
There is a similar question on Stack Overflow but the solution given for that question doesn't work correctly for me.
"use strict";
let canvas, width, height, ctx;
let fireworks = [];
let particles = [];
function setup() {
canvas = document.getElementById("canvas");
setSize(canvas);
ctx = canvas.getContext("2d");
ctx.fillStyle = "#000000";
ctx.fillRect(0, 0, width, height);
fireworks.push(new Firework(Math.random()*(width-200)+100));
window.addEventListener("resize",windowResized);
document.addEventListener("click",onClick);
}
setTimeout(setup,1);
function loop(){
ctx.globalAlpha = 0.1;
ctx.fillStyle = "#000000";
ctx.fillRect(0, 0, width, height);
ctx.globalAlpha = 1;
for(let i=0; i<fireworks.length; i++){
let done = fireworks[i].update();
fireworks[i].draw();
if(done) fireworks.splice(i, 1);
}
for(let i=0; i<particles.length; i++){
particles[i].update();
particles[i].draw();
if(particles[i].lifetime>80) particles.splice(i,1);
}
if(Math.random()<1/60) fireworks.push(new Firework(Math.random()*(width-200)+100));
}
setInterval(loop, 1/60);
//setInterval(loop, 100/60);
class Particle{
constructor(x, y, col){
this.x = x;
this.y = y;
this.col = col;
this.vel = randomVec(2);
this.lifetime = 0;
}
update(){
this.x += this.vel.x;
this.y += this.vel.y;
this.vel.y += 0.02;
this.vel.x *= 0.99;
this.vel.y *= 0.99;
this.lifetime++;
}
draw(){
ctx.globalAlpha = Math.max(1-this.lifetime/80, 0);
ctx.fillStyle = this.col;
ctx.fillRect(this.x, this.y, 2, 2);
}
}
class Firework{
constructor(x){
this.x = x;
this.y = height;
this.isBlown = false;
this.col = randomCol();
}
update(){
this.y -= 3;
if(this.y < 350-Math.sqrt(Math.random()*500)*40){
this.isBlown = true;
for(let i=0; i<60; i++){
particles.push(new Particle(this.x, this.y, this.col))
}
}
return this.isBlown;
}
draw(){
ctx.globalAlpha = 1;
ctx.fillStyle = this.col;
ctx.fillRect(this.x, this.y, 2, 2);
}
}
function randomCol(){
var letter = '0123456789ABCDEF';
var nums = [];
for(var i=0; i<3; i++){
nums[i] = Math.floor(Math.random()*256);
}
let brightest = 0;
for(var i=0; i<3; i++){
if(brightest<nums[i]) brightest = nums[i];
}
brightest /=255;
for(var i=0; i<3; i++){
nums[i] /= brightest;
}
let color = "#";
for(var i=0; i<3; i++){
color += letter[Math.floor(nums[i]/16)];
color += letter[Math.floor(nums[i]%16)];
}
return color;
}
function randomVec(max){
let dir = Math.random()*Math.PI*2;
let spd = Math.random()*max;
return{x: Math.cos(dir)*spd, y: Math.sin(dir)*spd};
}
function setSize(canv){
canv.style.width = (innerWidth) + "px";
canv.style.height = (innerHeight) + "px";
width = innerWidth;
height = innerHeight;
canv.width = innerWidth*window.devicePixelRatio;
canv.height = innerHeight*window.devicePixelRatio;
canvas.getContext("2d").scale(window.devicePixelRatio, window.devicePixelRatio);
}
function onClick(e){
fireworks.push(new Firework(e.clientX));
}
function windowResized(){
setSize(canvas);
ctx.fillStyle = "#000000";
ctx.fillRect(0, 0, width, height);
}
<canvas id="canvas"></canvas>
<h1>Hello</h1>
One solution is to use many layers, which will be responsible of the overall fading.
So at each frame, you create a new layer, and you decrease the opacity of all the previous ones. When that opacity is below a certain threshold, you remove it from the list.
Then you change your draw()
methods to register the drawing commands in these layers instead of making the context draw directly. The layer will be responsible of setting the correct alpha (based on its own opacity and the one of the item to be drawn), and it will draw all the items from its frame.
Basically these layers will be still of the current frame, but instead of storing the pixels data, we only store the drawing commands. This is quite easy in your situation since you have a rather limited number of drawing settings (only color, alpha, x, and y), but someone else may need a more complex API.
Anyway, here is a messy rewrite as a proof-of-concept:
class Layer {
constructor(ctx) {
this.ctx = ctx;
this.commands = [];
this.alpha = 1;
}
update(delta) {
this.alpha *= 1 - (0.1 * delta);
return this.alpha < 0.1;
}
draw() {
this.commands.forEach(([color, alpha, x, y, width, height]) => {
this.ctx.fillStyle = color;
this.ctx.globalAlpha = this.alpha * alpha;
this.ctx.fillRect(x, y, width, height);
});
}
}
class Particle {
constructor(x, y, col) {
this.x = x;
this.y = y;
this.col = col;
this.vel = randomVec(2);
this.lifetime = 0;
}
update(delta) {
delta = 1;
this.x += this.vel.x;
this.y += this.vel.y;
this.vel.y += 0.02 * delta;
this.vel.x *= 1 - (delta * 0.01);
this.vel.y *= 1 - (delta * 0.01);
this.lifetime += delta;
return this.lifetime > 80;
}
draw() {
const color = this.col;
const alpha = Math.max(1 - this.lifetime / 80, 0);
const x = this.x;
const y = this.y;
const rad = 2;
currentLayer.commands.push([color, alpha, x, y, rad, rad]);
}
}
class Firework {
constructor(x) {
this.x = x;
this.y = height;
this.isBlown = false;
this.col = randomCol();
}
update(delta) {
this.y -= 3 * delta;
if (this.y < 350 - Math.sqrt(Math.random() * 500) * 40) {
this.isBlown = true;
for (let i = 0; i < 60; i++) {
particles.push(new Particle(this.x, this.y, this.col))
}
}
return this.isBlown;
}
draw() {
const color = this.col;
const alpha = 1;
const x = this.x;
const y = this.y;
const rad = 2;
currentLayer.commands.push([color, alpha, x, y, rad, rad]);
}
}
const canvas = document.querySelector("canvas");
let width, height, currentLayer;
const layers = [];
const fireworks = [];
const particles = [];
setSize(canvas);
const ctx = canvas.getContext("2d");
fireworks.push(new Firework(Math.random() * (width - 200) + 100));
window.addEventListener("resize", windowResized);
document.addEventListener("click", onClick);
let lastTime = document.timeline?.currentTime || performance.now();
requestAnimationFrame(loop);
function loop(time) {
// values were defined with a 60FPS base
// we now use rAF and thus need a delta time
// to honor the same expected speed on all devices
const delta = (time - lastTime) / (1000 / 60);
lastTime = time;
ctx.globalAlpha = 1;
ctx.clearRect(0, 0, width, height);
currentLayer = new Layer(ctx);
layers.push(currentLayer);
const ended = [];
fireworks.forEach((firework, index) => {
const done = firework.update(delta);
if (done) {
ended.push(index);
}
firework.draw();
});
// remove all ended, for last to first
ended.reverse().forEach((index) => {
fireworks.splice(index, 1);
});
ended.length = 0;
particles.forEach((particle, index) => {
const done = particle.update(delta);
particle.draw();
if (done) {
ended.push(index);
}
});
ended.reverse().forEach((index) => {
particles.splice(index, 1);
});
ended.length = 0;
layers.forEach((layer, index) => {
const done = layer.update(delta);
if (done) {
ended.push(index);
}
layer.draw();
});
ended.reverse().forEach((index) => {
layers.splice(index, 1);
});
if (Math.random() < 1 / 60) {
fireworks.push(new Firework(Math.random() * (width - 200) + 100));
}
requestAnimationFrame(loop);
}
function randomCol() {
var letter = '0123456789ABCDEF';
var nums = [];
for (var i = 0; i < 3; i++) {
nums[i] = Math.floor(Math.random() * 256);
}
let brightest = 0;
for (var i = 0; i < 3; i++) {
if (brightest < nums[i]) brightest = nums[i];
}
brightest /= 255;
for (var i = 0; i < 3; i++) {
nums[i] /= brightest;
}
let color = "#";
for (var i = 0; i < 3; i++) {
color += letter[Math.floor(nums[i] / 16)];
color += letter[Math.floor(nums[i] % 16)];
}
return color;
}
function randomVec(max) {
let dir = Math.random() * Math.PI * 2;
let spd = Math.random() * max;
return {
x: Math.cos(dir) * spd,
y: Math.sin(dir) * spd
};
}
function setSize(canv) {
canv.style.width = (innerWidth) + "px";
canv.style.height = (innerHeight) + "px";
width = innerWidth;
height = innerHeight;
canv.width = innerWidth * window.devicePixelRatio;
canv.height = innerHeight * window.devicePixelRatio;
canvas.getContext("2d").scale(window.devicePixelRatio, window.devicePixelRatio);
}
function onClick(e) {
fireworks.push(new Firework(e.clientX));
}
function windowResized() {
setSize(canvas);
}
body{margin:0;/* checkered effect from https://stackoverflow.com/a/51054396/3702797 */
--tint:rgba(255,255,255,0.9);background-image:linear-gradient(to right,var(--tint),var(--tint)),linear-gradient(to right,black 50%,white 50%),linear-gradient(to bottom,black 50%,white 50%);background-blend-mode:normal,difference,normal;background-size:2em 2em;
}
canvas{display: block}
<canvas></canvas>