DEV Community

Nic
Nic

Posted on

SCSS and JS Fireworks

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:

@use "sass:math";

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

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.

function random(min, max) {
  return min + Math.random() * (max + 1 - min);
}
Enter fullscreen mode Exit fullscreen mode

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.

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)
  }  
}
Enter fullscreen mode Exit fullscreen mode

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.

.firework {
  position: absolute;
  width: 5px;
  height: 5px;
  opacity: 1;
}
Enter fullscreen mode Exit fullscreen mode

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.

@keyframes launchFirework {
  to { opacity: 0; }
}
Enter fullscreen mode Exit fullscreen mode

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.

@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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

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)
Enter fullscreen mode Exit fullscreen mode

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.

let set = 0

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

  set += 1
}
Enter fullscreen mode Exit fullscreen mode

Then we'll use another setInterval to work out when to delete fireworks

setInterval(deleteFirework, fireworkTime * 3)
Enter fullscreen mode Exit fullscreen mode

And to delete them we can remove the divs:

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

    oldFireworks.forEach(firework => {
      firework.remove();      
    });      
  }
}
Enter fullscreen mode Exit fullscreen mode

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

Latest comments (0)