SCSS and JS Fireworks
Tags: scss, javascript, tutorial
Published: 2022-02-20 10:13:15

A while ago I made some fireworks using HTML Canvas. But then I wondered if you could make them without the Canvas. Turns out, the answer is yes.

## Set up
The good news is that for the HTML we don't need anything! We'll use the body for the background and create divs in JavaScript.

We'll use SCSS, sass math and set up the background:
```css
@use "sass:math";

body {
  background-color:black;    
  height: 100%;
  width: 100%;
  margin: 0;
  overflow:hidden;
}
```
The sass math will come in later to add some randomness. For the background we want it to be black, fill the space and not have scrollbars.

And in JavaScript I'll use my handy random function that will give me a random number between min and max inclusive.
```javascript
function random(min, max) {
  return min + Math.random() * (max + 1 - min);
}
```

## Create fireworks
In JavaScript we'll create a firework using 50 divs. They'll all start at the centre and we'll use SCSS to animate them later. We'll start them at a random point on the body and with a random colour.

Since we'll want multiple fireworks, we'll put this all in a function, so we can call it multiple times.

```javascript
const createFirework = () => {
  const xPos = random(0, 100)
  const yPos = random(0, 100)
  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.style.backgroundColor = colour
    firework.style.left = xPos + '%'
    firework.style.top = yPos + '%'
    document.body.appendChild(firework)
  }  
}
```
In this function we're looping through 50 times to create our 50 divs. For each one we give it a class of firework and another class that includes the firework number. This is why we're starting our loop from 1, rather than 0. We'll use the second class in SCSS to make sure the divs don't all go in the same direction.

We also give the divs a random colour and a random position on the body between 0% and 100%.

In CSS we'll set the size of the div, make it position absolute, so the left and top styles we set in JavaScript will position it, and so they can all go on top of each other. And also set the opacity to 1 since we'll fade it out as it moves.
```css
.firework {
  position: absolute;
  width: 5px;
  height: 5px;
  opacity: 1;
}
```

## Animating the firework
There are two things we want to do, move the divs out from the centre in any direction, and fade it out. And we want that to happen within a random time. So we're going to need some CSS keyframe animation and some sass math.

```css
@keyframes launchFirework {
  to { opacity: 0; }
}
```
Here we're setting up a keyframe that we'll use to animate the divs from their original opacity of 1 to an opacity of 0.

```css
@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;
  }
}
```
Then here we're looping through each of our divs. Inside the loop we're setting up another keyframe to move the div. We want to translate both X and Y a random distance between -100px and 100px. When you use the random function in sass it will give a random number between 1 and the number in brackets. So random(201) gives you a random number between 1 and 201. So then we take 101 off it to get a random number between -100 and 100.

Then for each div we tell it to animate. We want the divs to take a random amount of time between 500ms and 1500ms, hence the random(1001) + 499.

Sass random is calculated when it's converted to CSS. Which means that those numbers will be different for each div, but the same for each set of 50 divs. The alternative is to write all the CSS in JavaScript, so it's random each time. But I didn't want to do that, as it feels more complicated. And this still looks good, so it didn't feel worth it.

## Multiple fireworks
So now we have one firework. But we want multiple fireworks. The way to do that is to call the createFirework function after a random amount of time.
```javascript
createFirework()

// The fireworks last between 500 and 1500 ms
// but we want lots on screen
// so we'll create a new one every 750ms

const fireworkTime = 750
setInterval(createFirework, fireworkTime)
```
Here we're creating a firework when the page first loads. Then using setInterval to create another firework every 750ms.

But there's a problem with this: it won't take too long before we'll have a lot of divs on the page, most of which have an opacity of 0. So we'll tidy things up and delete old fireworks.

In an ideal world we'll delete the divs once they have an opacity of 0. However, JavaScript just can't see that. But we know that they'll all be gone within 1500ms, so we can delete them then.

## Deleting old fireworks
To do this, we'll add in a counter so we know which firework set we're currently on. Since fireworkTime is set at 750ms, we'll delete the fireworks from 3 sets ago. The fireworks from 2 sets ago should all be at opacity 0, but let's err on the side of caution as we don't want to suddenly make any vanish from the page.

First we're going to count our sets. We'll set up the variable and increase it during the createFirework function. And add a class to the divs to tell us which set they're from.
```javascript
let set = 0

const createFirework = () => {
  for (let i = 1; i <= 50; i++) {
    firework.classList.add(`set${set}`)
  }

  set += 1
}
```
Then we'll use another setInterval to work out when to delete fireworks
```javascript
setInterval(deleteFirework, fireworkTime * 3)
```

And to delete them we can remove the divs:
```javascript
const deleteFirework = () => {
  const setToDelete = set - 3
  if (set >= 0) {
    const oldFireworks = document.querySelectorAll(`.set${setToDelete}`);

    oldFireworks.forEach(firework => {
      firework.remove();      
    });      
  }
}
```
Here we're making sure we're deleting three sets ago - and that three sets ago exist. No point trying to delete set -3 since it doesn't exist. Then we'll find the divs with a class of this set, loop through them and remove them.

## The final code
Here's the whole thing in CodePen

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