Fireworks on click
Tags: javascript
Published: 2022-05-01 11:45:17

A few months ago I wrote some fireworks using SCSS and JavaScript. But I thought, what if you could have fireworks on cue, where you click? Today is 1st May, which is a national holiday in many countries, so it seems like a good day for some fireworks, in celebration.

I'm starting with the fireworks I did before, and modifying them. So go and have a look at the previous post in this series if you're confused by any of the code I'm not explaining.

## The set up
The SCSS is very nearly the same as before:
```css
@use "sass:math";

html {
  height: 100%;
}

body {
  background-color:black;    
  height: 100%;
  width: 100%;
  margin: 0;
  overflow:hidden;
}

.firework {
  position: absolute;
  width: 5px;
  height: 5px;
  opacity: 1;
}

@keyframes launchFirework {
  to { opacity: 0; }
}

@for $i from 1 through 50 {
  @keyframes launchFirework#{$i} {
   to { transform: translate(random(201) - 101 + px, random(201) - 101 + px); }
  }
  .firework#{$i} {
    animation: launchFirework random(1001) + 499 + ms linear forwards, launchFirework#{$i} random(1001) + 499 + ms linear forwards;
  }
}
```
A brief description of this code:
We're setting a black background to simulate the sky and setting the fireworks to be absolutely positioned. Then using animation to spread them out and make them more transparent over time.

The addition is to set html to be height 100%. Without that it looks like the body is filing the screen since it's all black and the fireworks appear on it in the right place. But in reality it's 0px high because it doesn't have any content (the fireworks are absolutely positioned). Setting the html to be height 100% means that the body's setting of 100% means it does fill the screen.

Two of the JavaScript functions are the same:
```js
function random(min, max) {
  return min + Math.random() * (max + 1 - min);
}
```
This function is used to randomise a few things. It makes it easy to randomise between min and max inclusive: Math.random() by default does not include max.

```js
const deleteFirework = () => {
  const setToDelete = set - 3
  if (set >= 0) {
    const oldFireworks = document.querySelectorAll(`.set${setToDelete}`);

    oldFireworks.forEach(firework => {
      firework.remove();      
    });      
  }
}
```
After a while we will need to delete fireworks otherwise we'll quickly have thousands of divs. JavaScript can't tell if their opacity has reached 0, so here we make sure we delete fireworks from at least 3 sets ago

## Clicking the screen

Now we want an event listener to listen to a click on the body (hence the need to make sure the body fills the screen) and create a firework. And delete the firework from 3 sets ago:
```js
let set = 0

document.body.addEventListener('click', (event) => {
  deleteFirework()
  createFirework(event)
})
```

And then the createFirework function is very similar, but instead of randomly positioning the firework we position it where we clicked:
```js
const createFirework = (event) => {
  const xPos = event.clientX
  const yPos = event.clientY
  const colour = '#'+Math.random().toString(16).substr(2,6);
  
  // Create 50 divs, start them on top of each other
  // so they can radiate out from the centre
  for (let i = 1; i <= 50; i++) {
    const firework = document.createElement('div')
    firework.className = 'firework'
    firework.classList.add(`firework${i}`)
    firework.classList.add(`set${set}`)
    firework.style.backgroundColor = colour
    firework.style.left = xPos + 'px'
    firework.style.top = yPos + 'px'
    document.body.appendChild(firework)
  }  
  
  set += 1
}
```
There are two sets of changes here. The first is at the top:
```js
  const xPos = event.clientX
  const yPos = event.clientY
```
We passed the event to this function, so we can use it to pick up the mouse position when we clicked. clientX and clientY give us the horizontal and vertical position respectively.

There are a couple of different ways of getting the mouse position. client gets the position relative to the viewport and ignores scrolling. page gets the position relative to the page and includes scrolling. Since the body fills the page, both of those will give us the same number, so it doesn't matter which we use.

The other change is how we use xPos and yPos. Previously these were percentages, but now we want them to be pixels:
```js
    firework.style.left = xPos + 'px'
    firework.style.top = yPos + 'px'
```

## The final code
And that's it! Here's the code in CodePen. Have fun creating your own fireworks show! (clicking on the same spot multiple times is fun)

{% codepen https://codepen.io/nicm42/pen/ExQYryx %}