It's New Year, which means fireworks. Better than the usual animal-scaring fireworks are ones created using HTML Canvas.
There are many different ways of creating fireworks. The way I'm doing it starts from the ones demonstrated in the 2019 Royal Institution Christmas Lectures.
Set up
Let's do some setup with HTML and CSS, then we'll get into the JavaScript.
HTML:
<canvas id="canvas"></canvas>
CSS:
body {
background-color: black;
height: 100%;
width: 100%;
margin: 0;
overflow: hidden;
}
So here we've set up the canvas, ready to use, and made the body fill the screen with black to represent the sky.
Canvas set up
So now we'll set the canvas up and tell it to fill the screen:
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext("2d");
ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
Set up fireworks
The fireworks set up will go in a function, so each time we create fireworks we can make them with a new random position and colour.
function setupFireworks() {
let particles = [];
const startTime = new Date().getTime();
const startX = 50;
const startY = 50;
const colour = '#ff0000';
const time = 500;
let alpha = 1;
}
There's a lot of setup here, lets go through it.
- particles: each firework is going to be made up of individual squares that will be stored in the particles array. We start with an empty array and we'll fill it with particles when we draw the fireworks.
- startTime: we're going to show each firework for a short time only. In order to work out how long it's been, we can look at the date now vs when we start.
- startX and startY: this is the position of the firework on the screen. Eventually this will be random, but for now it's at 50, 50
- colour: the colour of the fireworks. Eventually this will be random, but for now it's my favourite colour.
- time: how long to show the firework for. Eventually this will be random, but for now it's 500ms
- alpha: we're going to fade the fireworks out, as real fireworks do. But they'll start with an alpha of 1.
Draw fireworks
Now the set up is done we can call drawFireworks - we call this from within setupFireworks so it has access to all the variables we set up.
function drawFireworks() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
let particle = {x: startX, y: startY, xVel: 5, yVel: 5}
particles.push(particle);
}
First we're clearing the canvas. Eventually we'll have each firework fade out and be replaced by another and we need to make sure it's cleared first.
Then we can set up our particle array. To start with, we have just one particle. It has an x position of startX and a y position of startY, which we set up earlier. Then it has an x velocity and y velocity. This is because the particles will move, like a real firework.
for(let p = 0; p < particles.length; p++){
particle = particles[p];
ctx.fillStyle = colour;
ctx.globalAlpha = alpha;
ctx.fillRect(particle.x, particle.y, 5, 5);
particle.x += particle.xVel;
particle.y += particle.yVel;
}
Here we loop through all those particles (for now it's just one, it will be more later). We set the fillStyle aka canvas colour to be the colour we set earlier. And the canvas's globalAlpha to be the alpha value we set earlier.
And then we draw a rectangle with fillRect. We set its x and y to be the x and y we set earlier, and make it 5px wide and 5px high.
We then increase its x and y position (ie down and to the right) by the velocities we set earlier, although for now that just moves it once. That's only changed in the array, not in fillRect.
If you've done everything right you should get a red square on your screen that's 5px by 5px and at 50px across from the left and 50px down from the top. Here's the whole JavaScript so far:
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext("2d");
ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
setupFireworks()
function setupFireworks() {
let particles = [];
const startTime = new Date().getTime();
const startX = 50;
const startY = 50;
const colour = '#ff0000';
const time = 500;
let alpha = 1;
drawFireworks();
function drawFireworks() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
let particle = {x: startX, y: startY, xVel: 5, yVel: 5}
particles.push(particle);
for(let p = 0; p < particles.length; p++){
particle = particles[p];
ctx.fillStyle = colour;
ctx.globalAlpha = alpha;
ctx.fillRect(particle.x, particle.y, 5, 5);
particle.x += particle.xVel;
particle.y += particle.yVel;
}
}
}
Making the firework move
What we do is to run drawFireworks again, which creates a new particle and adds it to the array. We then loop through the particles drawing them on the screen.
if(new Date().getTime() - startTime < time){
window.requestAnimationFrame(drawFireworks);
}
Here we're checking the current time vs startTime. If it's been less than 500ms, then we draw another firework. requestAnimationFrame is used to animate our drawing. We tell it to run drawFireworks again and it takes care of animating all the particles.
This isn't much of a firework yet, it's just a sequence of red squares going diagonally down the screen. Let's update alpha so it fades out a bit more every 100ms.
if(new Date().getTime() - startTime < time){
if(new Date().getTime() - startTime < time + 100){
alpha -= 0.01;
}
window.requestAnimationFrame(drawFireworks);
}
Now it looks like a bright red falling star. But fireworks should be more than just one rectangle.
Making it into a firework
What we need here is a bit of randomness. At the moment all the particles are in the same place. We want them to be in a random position around the start.
The easiest way to do this is to use a function. This will return a random integer between min and max inclusive:
function random(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
Then we can update the line where we create a particle to this:
let particle = {x: startX, y: startY, xVel: random(-5,5), yVel: random(-5,5)}
We then get a firework that radiates out from the centre. Which is pretty cool.
Adding some randomness
We can make the firework even cooler by adding in some randomness to the constants we set up earlier.
const startX = random(50, canvas.width-50);
const startY = random(50, canvas.height-50);
const colour = '#'+Math.random().toString(16).substr(2,6);
const time = random(500,1500);
So now our firework is going to be at a random position, a random colour and will be shown for a random amount of time.
For startX and startY I chose random numbers that are 50px away from the edge of the canvas to make sure there's space to see the firework.
The colour is not using our random function. Instead it's using Math.random to get a random number between 0 and 1, not including 1. We then convert it to a string. The parameter 16 means we get a hexadecimal number. Since colours are expressed in hex code, that hexadecimal number is pretty useful, when you know it's also known as 'hex'.
My random example has given me "0.712b1029ae4f8" which doesn't seem that useful. Except it is: we just need the 6 digits after the decimal place, which is what substr is doing. Then we put it after a # and there we have a hex colour. In this case, it's #712b10. Which is brown.
Then time is simple - it's going to show the firework for a random amount of time between 500ms and 1500ms.
Multiple fireworks
You don't usually get just one firework, you want more than that. All we have to do is to check when the time has run out and generate a new one:
if(new Date().getTime() - startTime < time){
if(new Date().getTime() - startTime < time + 100){
alpha -= 0.01;
}
window.requestAnimationFrame(drawFireworks);
} else{
setupFireworks();
}
Final code
Here is a CodePen showing all the code and the resulting fireworks.
Improvements
There's more that could be done with these fireworks. Could you show multiple at once? I tried and my computer couldn't cope, but it is old and slow. Some of the colours don't show up as well on black, so the random colour should deal with that. Maybe there could be stars and the moon and some scenery in the background.
Fork the CodePen and have a play. And link me to what you come up with, I'd love to see it.
Top comments (0)