JavaScript Fireworks

JavaScript Fireworks

I thought I’d do a post about one of my (much) older creative coding projects, so today I’ll be sharing with you a simple firework program I created in JavaScript a while ago. Here’s what the fireworks look like when the program is launched1:

Now for how it works2. The entire program is organized as a web page so it can be run in the browser. The HTML canvas element is used for rendering the fireworks.

All the information about each “firework” is stored in a series of arrays3. These include data such as the vertical and horizontal positions of each shell and the velocities of the resulting particles after they explode.

var fireworkX = [];
var fireworkY = [];
var fireworkTimer = [];
var fireworkExplodeTime = [];
	
var fireworkParticleX = [];
var fireworkParticleY = [];
var fireworkParticleVelocityX = [];
var fireworkParticleVelocityY = [];
var particleColor = 0;
var fireworkParticleColor = [];

A number of settings are also stored; these can be adjusted to produce different results in the simulation.

var colorPalette = [[255,0,0],[255,130,0],[255,255,0],[0,255,0],[0,0,255],[150,0,150]];
var fireworkSpeed = 6;
var particleAmount = 30;
var particleMin = 10;
var particleSize = 10;
var fireworkAmount = 1;
var particleVariationX = 4;
var particleVariationY = 6;
var explodeTimeLimit = 3;

Every frame, a function called drawScreen() is called. This function displays the fireworks and constantly updates their motion properties so that they behave correctly. One part of this function is the firework exploder4. This loop goes through every existing firework and checks if its explode timer has expired. If it has, the firework will be exploded: a random color is selected from the color pallet and a random number of particles are generated with random initial velocities. Finally, a pop sound is played and the original firework object is removed from memory.

for(i=0;i<fireworkX.length;i++){
	if(fireworkTimer[i]==fireworkExplodeTime[i]){
		
		particleColor = Math.floor(Math.random()*colorPalette.length);
		
		for(j=0;j<Math.random()*particleAmount+particleMin;j++){
			fireworkParticleX.push(fireworkX[i]);
			fireworkParticleY.push(fireworkY[i]);
			
			fireworkParticleVelocityX.push(Math.random()*particleVariationX*2-particleVariationX);
			fireworkParticleVelocityY.push(Math.random()*particleVariationY-particleVariationY*2);
			
			fireworkParticleColor.push(particleColor)
		}
		
		var audio = new Audio('pop.mp3');
		audio.play();
		fireworkX.splice(i,1);
		fireworkY.splice(i,1);
		fireworkTimer.splice(i,1);
		fireworkExplodeTime.splice(i,1);
	}
	else{
		fireworkTimer[i]++;
	}
}

The canvas is cleared to prepare the next frame of the simulation to be rendered:

ctx.beginPath();
ctx.fillStyle = "rgba(0,0,0,1)";

ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.fill();

Then, each existing (nonexploded) firework is rendered onto the canvas as a circle using ctx.arc() and ctx.fill().

for(i=0;i<fireworkX.length;i++){
		
	ctx.beginPath();
	ctx.fillStyle = "rgba(255,255,0,1)";
		
	ctx.arc(fireworkX[i],fireworkY[i],10,0,2 * Math.PI,false);
	ctx.fill();

}

As well as the particles produced by exploding fireworks.

for(i=0;i<fireworkParticleX.length;i++){

	ctx.fillStyle = "rgba("+colorPalette[fireworkParticleColor[i]][0]+","+colorPalette[fireworkParticleColor[i]][1]+","+colorPalette[fireworkParticleColor[i]][2]+",1)";
	ctx.beginPath();
		
	ctx.arc(fireworkParticleX[i],fireworkParticleY[i],particleSize,0,2 * Math.PI,false);
	ctx.fill();

}

Then the physics for both the fireworks and particles are updated. Each firework moves up until its explode timer ends. The firework particles have their own x and y velocity values stored, which are used to calculate the frame-to-frame movement of each particle. The downward y-velocity of these particles is increased slightly over time to show acceleration towards the ground.

for(i=0;i<fireworkX.length;i++){
	fireworkY[i] -= fireworkSpeed;
}

for(i=0;i<fireworkParticleX.length;i++){
	fireworkParticleX[i] += fireworkParticleVelocityX[i];
	fireworkParticleY[i] += fireworkParticleVelocityY[i];
	
	fireworkParticleVelocityY[i] += 0.25;
}

Additionally, new fireworks are added to the scene at random intervals.

if(Math.random()*100<fireworkAmount){
	addFirework();
}

This code calls another function, addFirework(), which initialize a firework with a random x-position and explode timer5.

function addFirework(){
	fireworkX.push(Math.random()*canvas.width);
	fireworkY.push(canvas.height);
	fireworkTimer.push(0);
	fireworkExplodeTime.push(Math.round(((Math.random()*(canvas.height/explodeTimeLimit))+canvas.height/explodeTimeLimit)/fireworkSpeed));
}

Once particles have left the screen, they are also removed:

for(i=0;i<fireworkParticleX.length;i++){
	if(fireworkParticleY[i]>canvas.height){	
		fireworkParticleX.splice(i,1);
		fireworkParticleY.splice(i,1);
		
		fireworkParticleVelocityX.splice(i,1);
		fireworkParticleVelocityY.splice(i,1);
		
		fireworkParticleColor.splice(i,1);
	}
}

New fireworks can also be added to the screen using the space bar or clicking. Two event listeners are added to achieve this:

document.body.onkeyup = function(addFireworkSpace){
	if(addFireworkSpace.keyCode == 32){
		addFirework();
	}
}

document.getElementById("canvas").onclick = function addFireworkClick(){
	addFirework();
}

Finally, the screen rendering function is executed at a maximum of 100 times a second (once every 10 milliseconds)6.

setInterval(drawScreen,10);

That’s about it. You can try the program out for yourself here, and the full source code is available on GitHub for anyone who wants to take a look at it or experiment with it. Thanks for reading, and I’ll see you in the next post.

  1. In fancy animated gif format.
  2. With some slight simplification. For full documentation, see the original code. I’ll comment it eventually.
  3. I probably would have organized the code somewhat differently if I redid this project today.
  4. What would you call it?
  5. Edit: sorry for the math gore on line 43, I was still pretty new to JavaScript when I wrote this code. Apparently this was also a time when I thought line spacing should be proportional to the depth of the functions. And that inline comments are in any way okay.
  6. In reality, this is done much slower depending on the web browser settings and available processing power.