Make an explosive firework display

Tutorial

Time required : 1 hour
Pre-requisites : Intermediate JavaScript, canvas experience useful
What is covered : Particle systems, simple physics and easing

Creating fireworks using the HTML 5 canvas: both awesome and fun to do. And that’s exactly what we’ll be doing today! As you can see from the description above, we’ll be covering particles, physics and easing so even if fireworks aren’t your thing (there’s the door!) there should still be a lot of transferable coding goodness.

Note : there is currently a performance issue on Firefox and it seems to be something to do with the ‘lighter’ composite operation mixed with alpha blending. We’re working hard to see if we can improve things and we’ll be in touch with Mozilla too to see if they can help! But in the meantime, sadly this code works much better in WebKit-based browsers.

Let’s start by talking about what we’re trying to achieve:

  1. We launch fireworks into the air
  2. At some point, with them being fireworks, they’re going to explode
  3. We need to render everything on screen (or this will be the worst fireworks show of all time)

It’s really that simple! The code that we’ll be talking about is available here, and it would be good to have that open alongside this so you can see exactly what I’m talking about.

Particles

Let’s start by talking about particles. Particles sound like they should be difficult to understand, but actually that’s not the case.

The way I think about them is a tiny little speck, and that’s about it. Because they’re a speck, though, they are subject to forces and collisions, so they can be pretty handy in terms of simulating fluids, smoke and all manner of things. In our case, they’re very handy for modelling a firework!

We can visualise that our firework shooting up into the air is a particle and, when it explodes, that each little bit that flies off is also a particle. I’ll also say at this point that we are going to be using two different animation methods for our particles: a target-based easing version for before the firework explodes (because we want a lot of control over where it goes on screen) and a physics based version after it explodes (because we’re happy for things to look natural and behave as they would in the real world). It’s great to understand both ways of controlling animation, and also when it’s best to use one or the other. If you’re confused over what I’m talking about it should all make sense once we’re done.

So since we’ve talked about particles let’s have a look at the JavaScript responsible:

216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
/**
 * Represents a single point, so the firework being fired up
 * into the air, or a point in the exploded firework
 */
var Particle = function(pos, target, vel, marker, usePhysics) {
 
  // properties for animation
  // and colouring
  this.GRAVITY  = 0.06;
  this.alpha    = 1;
  this.easing   = Math.random() * 0.02;
  this.fade     = Math.random() * 0.1;
  this.gridX    = marker % 120;
  this.gridY    = Math.floor(marker / 120) * 12;
  this.color    = marker;
 
  this.pos = {
    x: pos.x || 0,
    y: pos.y || 0
  };
 
  this.vel = {
    x: vel.x || 0,
    y: vel.y || 0
  };
 
  this.lastPos = {
    x: this.pos.x,
    y: this.pos.y
  };
 
  this.target = {
    y: target.y || 0
  };
 
  this.usePhysics = usePhysics || false;
 
};

Of note are the pos and vel properties. These describe the position and velocity of our particles. The rest is pretty much all window dressing for rendering, so we’ll come back to that in a bit.

We have a particle, but that doesn’t really mean a lot. After all, it’s not exactly doing anything. For that we really need to take a step back and look at the bigger picture.

To begin with we have to set everything up, which you’ll see in the initialize method:

34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
 * Create DOM elements and get your game on
 */
function initialize() {
 
  // start by measuring the viewport
  onWindowResize();
 
  // create a canvas for the fireworks
  mainCanvas = document.createElement('canvas');
  mainContext = mainCanvas.getContext('2d');
 
  // and another one for, like, an off screen buffer
  // because that's rad n all
  fireworkCanvas = document.createElement('canvas');
  fireworkContext = fireworkCanvas.getContext('2d');
 
  // set up the colours for the fireworks
  createFireworkPalette(12);
 
  // set the dimensions on the canvas
  setMainCanvasDimensions();
 
  // add the canvas in
  document.body.appendChild(mainCanvas);
  document.addEventListener('mouseup', createFirework, true);
  document.addEventListener('touchend', createFirework, true);
 
  // and now we set off
  update();
}

In here you can see that we’re creating a couple of canvases and attaching some event handlers for touch and mouse events. At the end of the function we call update(), and that’s where it actually gets a little more interesting.

You’ll see around the code that we have one array called particles. This array contains all the currently active particles. When we create a new firework, we push on a particle, and when that firework explodes we push on a whole heap of particles, one to represent each exploded piece!

So with that said, what actually happens inside the update function?

111
112
113
114
115
116
117
118
/**
 * The main loop where everything happens
 */
function update() {
  clearContext();
  requestAnimFrame(update);
  drawFireworks();
}

Not a lot, it seems. Clear the canvas context, draw the fireworks, schedule the next update. Let’s take each bit in turn.

Clearing the canvas is pretty straight forward. We fill the entire canvas with a semi-transparent black rectangle:

120
121
122
123
124
125
126
127
/**
 * Clears out the canvas with semi transparent
 * black. The bonus of this is the trails effect we get
 */
function clearContext() {
  mainContext.fillStyle = "rgba(0,0,0,0.2)";
  mainContext.fillRect(0, 0, viewportWidth, viewportHeight);
}

The reason we don’t fill it with opaque black is so that we can see a bit of the previous frames that were drawn. Over time this gives us trailing effects, which are very handy in this case since fireworks leave trails in the sky as they explode!

Next we call requestAnimFrame, which is a way of scheduling up the next update. You could call setInterval or setTimeout to do something similar, but requestAnimFrame is the best way to do animation in the browser. If it’s not supported the code we’re using will fall back to setTimeout – great!

Finally we draw the fireworks:

129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/**
 * Passes over all active particles
 * and draws them
 */
function drawFireworks() {
  var a = particles.length;
 
  while(a--) {
    var firework = particles[a];
 
    // if the update comes back as true
    // then our firework should explode
    if(firework.update()) {
 
      // kill off the firework, replace it
      // with the particles for the exploded version
      particles.splice(a, 1);
 
      // if the firework isn't using physics
      // then we know we can safely(!) explode it... yeah.
      if(!firework.usePhysics) {
 
        if(Math.random() < 0.8) {
          FireworkExplosions.star(firework);
        } else {
          FireworkExplosions.circle(firework);
        }
      }
    }
 
    // pass the canvas context and the firework
    // colours to the
    firework.render(mainContext, fireworkCanvas);
  }
}

As you can see that involves looping through the particles array, calling update against each firework and then rendering. What’s interesting inside there is that update returns a true or false. We’ll talk about why that is in a little bit. Again, though, nothing too crazy right?

If you’re wondering why I’m going backwards through the array, this is a micro-optimisation purely for speed. While loops going backwards are often faster than for loops going forward, but really you’ll only see benefits if you’ve got a lot of particles. As it happens I’m something of a sucker for any optimisations I can think of in my main loops, and this one is something of a habit for me!

Now that we’ve talked about the overall scheme of things, let’s talk about the fireworks in more detail. We need to talk about three things: colour, pre-explosion and the explosion.

Colour me impressed

Here’s the bigGlow image we’re using for our particles :

A fuzzy white glow. Well, actually is a fuzzy transparent glow, with an opaque black outline. We’ll create a single off-screen canvas and draw a sheet of different coloured particle images into it.

For each colour, we draw a solid coloured rectangle, and then draw the particle glow on top of it. As our bigGlow image is transparent in the middle, our colour shows through it.

Here’s the code:

74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
 * Creates a block of colours for the
 * fireworks to use as their colouring
 */
function createFireworkPalette(gridSize) {
 
  var size = gridSize * 10;
  fireworkCanvas.width = size;
  fireworkCanvas.height = size;
  fireworkContext.globalCompositeOperation = 'source-over';
 
  // create 100 blocks which cycle through
  // the rainbow... HSL is teh r0xx0rz
  for(var c = 0; c < 100; c++) {
 
    var marker = (c * gridSize);
    var gridX = marker % size;
    var gridY = Math.floor(marker / size) * gridSize;
 
    fireworkContext.fillStyle = "hsl(" + Math.round(c * 3.6) + ",100%,60%)";
    fireworkContext.fillRect(gridX, gridY, gridSize, gridSize);
    fireworkContext.drawImage(
      Library.bigGlow,
      gridX,<a href="http://creativejs.com/wp-content/uploads/2011/12/firework-palette.png"><img src="http://creativejs.com/wp-content/uploads/2011/12/firework-palette.png" alt="" title="firework-palette" width="121" height="121" class="alignnone size-full wp-image-2301" /></a>
      gridY);
  }
}

and it gives us a block that looks like this:

Fireworks Image Palette

So hopefully you can see how that works. What is really useful in this function is the HSL, which stands for hue-saturation-lightness. It’s a far more convenient way of choosing colours in this instance than RGB because it allows us to se a fixed saturation (100%) and lightness (60%) and then we simply cycle through the hues (through the rainbow – whee!) to get our blocks of colours.

Now we can use this to look up a source particle image for each colour for the rainbow. One canvas, a set of coloured particle images, looks we’ve got a solution!

Before you explode, let me just say…

When we create a firework and add it to our array of firework particles, it looks a little like this:

165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
/**
 * Creates a new particle / firework
 */
function createParticle(pos, target, vel, color, usePhysics) {
 
  pos = pos || {};
  target = target || {};
  vel = vel || {};
 
  particles.push(
    new Particle(
      // position
      {
        x: pos.x || viewportWidth * 0.5,
        y: pos.y || viewportHeight + 10
      },
 
      // target
      {
        y: target.y || 150 + Math.random() * 100
      },
 
      // velocity
      {
        x: vel.x || Math.random() * 3 - 1.5,
        y: vel.y || 0
      },
 
      color || Math.floor(Math.random() * 100) * 12,
 
      usePhysics)
  );
}

You’ll see in the code that I’m using dynamic objects to represent the position, velocity and targets. This is simply a way of grouping up the related properties.

createParticle is unusual in that it’s called twice, once with parameters and once without. When a firework is first created, the function is called without parameters. That’s why you see at the top of the function that it defaults all the values to some basic ones, which in this case puts the firework at the bottom middle of the screen. Later on the same function is called again when the firework explodes. In this case we do set the parameters as we want to be very explicit about the particle positions and velocities.

You can see that we push a new particle into the array with a position and velocity. But you’ll also notice that we set a target. This is because when we are pre-explosion we want the firework to fly to a fixed point on screen, somewhere between 150 and 250 pixels from the top of the screen. The alternative to this would be to model the physics and calculate what velocity the firework would need to get to the same place on screen. This would cause my head to explode, never mind the firework.

So how does setting the target help? Well, if you remember we call update on every firework during the loop. Inside that function you can see that we calculate the distance of the firework to the target and ease it towards that position:

255
256
257
258
var distance = (this.target.y - this.pos.y);
 
// ease the position
this.pos.y += distance * (0.03 + this.easing);

Now we have a convenient way of getting to our target, we also need to know when it’s arrived, because that’s the time when we want it to explode!

To do that we also take advantage of another feature of fireworks: as they fly they often fade to nothing just before exploding. So what we do is we tie the alpha value of the firework to the distance we calculated above:

283
this.alpha = Math.min(distance * distance * 0.00005, 1);

As distance gets smaller so does the alpha value. At the end of the function we have this line:

288
return (this.alpha < 0.005);

If the alpha value is very low we return true. This signals back to the drawFireworks function that this firework is ready to explode. Awesome.

Explode!

We’re in a great place, we’re ready to explode our firework. For this part I’m going to discuss one of the two options that our firework will take for exploding, and I’ll let you dissect the alternative.

Back in our drawFireworks function, when the update function returns true we remove our original firework particle and replace it with a whole heap of new ones:

154
FireworkExplosions.circle(firework);

This line is the key, as we are calling a function in our as-yet-unmentioned FireworkExplosions object. All this does is provide a function for creating the new particles for the exploded particle:

340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
/**
 * Explodes in a roughly circular fashion
 */
circle: function(firework) {
 
  var count = 100;
  var angle = (Math.PI * 2) / count;
  while(count--) {
 
    var randomVelocity = 4 + Math.random() * 4;
    var particleAngle = count * angle;
 
    Fireworks.createParticle(
      firework.pos,
      null,
      {
        x: Math.cos(particleAngle) * randomVelocity,
        y: Math.sin(particleAngle) * randomVelocity
      },
      firework.color,
      true);
  }
}

To achieve the effect we divide a circle into 100 slices, and we create a particle for each slice:

352
353
354
355
356
357
358
359
360
Fireworks.createParticle(
  firework.pos,
  null,
  {
    x: Math.cos(particleAngle) * randomVelocity,
    y: Math.sin(particleAngle) * randomVelocity
  },
  firework.color,
  true);

You’ll notice the use of cosine and sine function calls. What this does is give the new particle a velocity that acts in the direction from the centre of the circle to the edge of that slice. If that makes no sense, don’t worry! It’s worth looking at polar coordinates if you ever need a better breakdown of what’s going on.

If you look a bit further down below the circle function you’ll see there’s another function which causes the firework to explode in a star shape instead of a plain old circle. You could, in theory, add any number of functions to this object: boxes, letters, anything! As I said I’ll let you look over the details of the star function to work out how that one is done, but if you create new explosive shapes let me know!

Now that we’ve created all these particles, we apply gravity to them as part of the update. That means that they’ll fall towards the floor or, as we like to call it, the bottom of the browser!

267
268
269
270
271
272
273
274
275
if(this.usePhysics) {
  this.vel.y += this.GRAVITY;
  this.pos.y += this.vel.y;
 
  // since this value will drop below
  // zero we'll occasionally see flicker,
  // ... just like in real life! Woo! xD
  this.alpha -= this.fade;
}

Rendering

To this point we’ve got fireworks launching and exploding, but if we want to see anything we need to draw them onto the canvas. Let’s take a look at the actual rendering, since that’s how we get pretty stuff on screen.

299
300
context.globalCompositeOperation = 'lighter';
context.globalAlpha = Math.random() * this.alpha;

After a little preamble, we set the composite operation to lighter, which is the same as additive blending – new images get added on top of the colours below and this makes the colours burn out to white. This is equivalent to the Photoshop lighten blend mode, and very in-keeping with our firework look. Then we set the alpha to a random value which makes the fireworks flicker.

Next we draw a trail from the last known position of the firework to the new. This just adds the the movement effect:

302
303
304
305
306
307
308
309
310
311
// draw the line from where we were to where
// we are now
context.fillStyle = "rgba(255,255,255,0.3)";
context.beginPath();
context.moveTo(this.pos.x, this.pos.y);
context.lineTo(this.pos.x + 1.5, this.pos.y);
context.lineTo(this.pos.x + xVel, this.pos.y + yVel);
context.lineTo(this.pos.x - 1.5, this.pos.y);
context.closePath();
context.fill();

Finally we draw a small glow and then the chunk of our coloured firework canvas at the particle’s position.

313
314
315
316
317
// draw in the images
context.drawImage(Library.smallGlow, x - 3, y - 3);
context.drawImage(fireworkCanvas,
  this.gridX, this.gridY, 12, 12,
  x - 6, y - 6, 12, 12);

And that’s it!

Conclusion

I hope you’ve enjoyed this explanation of how to create this fireworks effect, and I hope you’ll maybe give it a try yourself.

I’d also like to point out that Seb runs fantastic CreativeJS courses where he covers these kinds of particle effects, and a whole heap more besides. (He didn’t ask me to put this in, by the way, I just figured it’s super relevant!) You should totally check those out and if you can make one, I’d highly recommend it.