Thunderstorm

A thunderstorm in your browser

polytroper@polytroper
Edit this page!

Ever wanted your own private, portable thunderstorm?

Well I have, so I built one—check it out here.

Example of this project

All you need is three simple parts:

  • A flashing background for lightning
  • Some audio clips for rain/thunder
  • An image for the foreground

Plus some simple JavaScript to flash the background and play thunder when you click.

Click here to begin.

(Tech used: Basic HTML/CSS, setTimeout, Howler, Math.random)

Let’s Make a Sky

To start, I’ve given you some audio clips, an image, and a basic HTML file with 3 parts:

<style>
  /* Your CSS will go here... */
</style>

<body>
  <!-- Your HTML will go here... -->
</body>

<script>
  // Your JS will go here...
</script>

We’ll use the body element as our sky. Let’s give it some color in our style section

<style>
  body {
    background: MidnightBlue;
  }
</style>

Now if you hit Run, you should see a dark blue sky.

Make it Flash

Now let’s use js to make the sky change colors.

<script>
  var body = document.body

  body.style.background = 'white'
</script>

Run the site—the background should immediately turn white.

We want each click to trigger a flash, so let’s create a flashOn function and “call” (trigger) it whenever we click anywhere in the window.

<script>
  var body = document.body

  function flashOn() {
    console.log('Flashing On')
    body.style.background = 'white'
  }

  // This Event Listener will trigger a flash with every click
  window.addEventListener('click', flashOn)
</script>

Of course, we also want to reset the background color. So we create a flashOff function.

<script>
  var body = document.body

  function flashOn() {
    console.log('Flashing On')
    body.style.background = 'white'
  }

  function flashOff() {
    console.log('Flashing Off')
    body.style.background = 'MidnightBlue'
  }

  // This Event Listener will trigger a flash with every click
  window.addEventListener('click', flashOn)
</script>

However, we want the screen to stay white for a brief moment.

We'll use a function called setTimeout to do this. setTimeout is like a timer—you give it the name of a function and a number, and your browser will wait that many milliseconds and then call that function.

So if we put setTimeout(flashOff, 10) inside flashOn, the browser will call flashOff 10 milliseconds after flashOn.

// ...

function flashOn() {
  console.log('Flashing On')
  body.style.background = 'white'

  // setTimeout will call flashOff after 10ms
  setTimeout(flashOff, 10)
}

Now hit Run, click the sky, and you should get a flash!

Make it Rain

Let’s make some sounds. We’ll use a tool called Howler, which makes it really simple to work with sound in js.

Copy in this script element, just after the body and just before your other script. This will import Howler so you can use it in your project.

<body>
  <!--...-->
</body>

<script src="https://cdnjs.cloudflare.com/ajax/libs/howler/2.0.15/howler.min.js"></script>

<script>
  // ...
</script>

Now create a new "Howl" at the top of your script for the file I called "rain.mp3". I also set the volume to 0.2 (20%).

<script>
  var rain = new Howl({
    src: 'rain.mp3',
    volume: 0.2
  })

  // ...
</script>

Now we just need to call rain.play() to start the show.

We could put that in the flashOn function. But we only want this sound to play the first time a user clicks.

So instead we'll create a new function called click which will trigger rain.play() once, and flashOn every time. We'll also create a boolean (true/false) variable called firstClick.

<script>
  // ...

  var firstClick = true

  function click() {
    console.log('Clicking')
    flashOn()

    // If this is the first click, start the rain and set firstClick to false.
    if (firstClick) {
      rain.play()
      firstClick = false
    }
  }

  // This Event Listener will trigger a flash with every click
  window.addEventListener('click', click)
</script>

So your full script should look like…

<script>
  var rain = new Howl({
    src: 'rain.mp3',
    volume: 0.2
  })

  var body = document.body

  function flashOn() {
    console.log('Flashing On')
    body.style.background = 'white'

    // setTimeout will call flashOff after 10ms
    setTimeout(flashOff, 10)
  }

  function flashOff() {
    console.log('Flashing Off')
    body.style.background = 'MidnightBlue'
  }

  var firstClick = true

  function click() {
    console.log('Clicking')
    flashOn()

    // If this is the first click, start the rain and set firstClick to false.
    if (firstClick) {
      rain.play()
      firstClick = false
    }
  }

  // This Event Listener will trigger a flash with every click
  window.addEventListener('click', click)
</script>

Now if you hit Play and click the sky, the rain should start after a single flash—just make sure your sound is on!

Make it Storm

Okay, now let's turn up the storminess with a big thunderclap at the start.

First we create a new Howl for "thunder_start.mp3", and a playBigThunder function that does exactly what is sounds like.

<script>
  var rain = new Howl({
    src: 'rain.mp3',
    volume: 0.2
  })

  var bigThunder = new Howl({
    src: 'thunder_start.mp3',
    volume: 0.5
  })

  function playBigThunder() {
    console.log('Playing Big Thunder')
    bigThunder.play()
  }

  // ...
</script>

Then we'll trigger the opening thunderclap on the first click, using setTimeout again for a 1 second delay:

function click() {
  console.log('Clicking')
  flashOn()

  // If this is the first click, start the rain and set firstClick to false.
  if (firstClick) {
    rain.play()
    firstClick = false

    // Play the starting thunder after a 1000ms delay
    setTimeout(thunderStart.play, 1000)
  }
}

Make it Random

For everything after the first click, let's play a randomized thunder sound.

I packed four random thunder sounds into one file called "thunder_sprites.mp3". Go ahead and listen to it in the Files section on the left side of your repl.it project.

Each sound is 8 seconds long; so 0–8s is Thunder A, 8–16s is Thunder B, and so on. I broke the sound up into 4 "sprites" so the computer loads one file instead of four.

Let's create a Howl for "thunder_sprites.mp3" with an option called sprites, which gives the time range for each thunder sound in milliseconds.

We'll also create a function called playRandomThunder, which does exactly what it sounds like. This function uses Math.random(), which provides a random number between 0 and 1. It also uses Math.floor(), which rounds a number down.

<script>
  // ...

  function playBigThunder() {
    console.log('Playing Big Thunder')
    bigThunder.play()
  }

  var thunder = new Howl({
    src: ['thunder_sprites.mp3'],
    // Four sprites, each 8 seconds
    sprite: {
      a: [0, 8000],
      b: [8000, 16000],
      c: [16000, 24000],
      d: [24000, 32000]
    },
    volume: 0.5
  })

  function playRandomThunder() {
    console.log('Playing Random Thunder')
    // This will create a random number between 0 and 4 and round it down
    var index = Math.floor(Math.random() * 4)
    // This will pick 'a' for index=0, 'b' for index=1, etc.
    var sprite = 'abcd'[index]
    // This will play the randomly-chosen thunder sprite
    thunder.play(sprite)
  }

  // ...
</script>

Then we'll trigger it on everything but the first click, after a 2–5s delay.

function click() {
  console.log('Clicking')
  flashOn()

  // If this is the first click, start the rain and set firstClick to false.
  if (firstClick) {
    rain.play()
    firstClick = false

    // Play the starting thunder after a 1000ms delay
    setTimeout(thunderStart.play, 1000)
  }
  // Otherwise, play thunder after a random delay between 2000ms and 5000ms
  else {
    var delay = 2000 + Math.floor(Math.random() * 3000)
    setTimeout(playRandomThunder, delay)
  }
}

Now if you hit Play and click the sky, you will get randomized thunder sounds!

Your full script should look like this:

<script>
  var rain = new Howl({
    src: 'rain.mp3',
    volume: 0.2
  })

  var bigThunder = new Howl({
    src: 'thunder_start.mp3',
    volume: 0.5
  })

  function playBigThunder() {
    console.log('Playing Big Thunder')
    bigThunder.play()
  }

  var thunder = new Howl({
    src: ['thunder_sprites.mp3'],
    // Four sprites, each 8 seconds
    sprite: {
      a: [0, 8000],
      b: [8000, 16000],
      c: [16000, 24000],
      d: [24000, 32000]
    },
    volume: 0.5
  })

  function playRandomThunder() {
    console.log('Playing Random Thunder')
    // This will create a random number between 0 and 4 and round it down
    var index = Math.floor(Math.random() * 4)
    // This will pick 'a' for index=0, 'b' for index=1, etc.
    var sprite = 'abcd'[index]
    // This will play the randomly-chosen thunder sprite
    thunder.play(sprite)
  }

  var body = document.body

  function flashOn() {
    console.log('Flashing On')
    body.style.background = 'white'

    // setTimeout will call flashOff after 10ms
    setTimeout(flashOff, 10)
  }

  function flashOff() {
    console.log('Flashing Off')
    body.style.background = 'MidnightBlue'
  }

  var firstClick = true

  function click() {
    console.log('Clicking')
    flashOn()

    // If this is the first click, start the rain and set firstClick to false.
    if (firstClick) {
      rain.play()
      firstClick = false

      // Play the starting thunder after a 1000ms delay
      setTimeout(playBigThunder, 1000)
    }
    // Otherwise, play thunder after a random delay between 2000ms and 5000ms
    else {
      var delay = 2000 + Math.floor(Math.random() * 3000)
      setTimeout(playRandomThunder, delay)
    }
  }

  // This Event Listener will trigger a flash with every click
  window.addEventListener('click', click)
</script>

Make it Flashier

Real lightning doesn't flash just once. At the end of each flash, there should be a chance for one more flash; that way some strikes will flash once, but most will flash 3 or 5 or 8 times.

Let's add some code to our flashOn function to trigger another flash 75% of the time.

function flashOn() {
  console.log('Flashing On')
  body.style.background = 'white'

  // setTimeout will call flashOff after 10ms
  setTimeout(flashOff, 10)

  // There is a 75% chance that each flash will trigger another flash
  if (Math.random() < 0.75) {
    setTimeout(flashOn, 100)
  }
}

Let's add some time variation—it looks unnatural when each flash is exactly 100ms from the last one.

We'll change that 100 to a randomized value between 50 and 500.

// There is a 75% chance that each flash will trigger another flash
if (Math.random() < 0.75) {
  setTimeout(flashOn, 50 + Math.random() * 450)
}

Finishing Touches

Finally, let's set the mood a little bit. A good thunderstorm needs a good landscape.

We’ll add some ground to complement our sky using HTML. Add a div inside body and name it "ground":

<body>
  <div id="ground"></div>
</body>

Now we need to give it some color and make sure it fills some space at the bottom of the page.

In our style section, we’ll set this div to be:

  • position: absolute;, which lets us set the position and size directly
  • left: 0; right: 0; bottom: 0;, which means the left, right, and bottom sides will touch the edges of the page
  • height: 100px;, which makes it 100px high
  • background: black; which makes it a nice black silhouette
<style>
  /* ... */

  #ground {
    position: absolute;

    left: 0;
    right: 0;
    bottom: 0;
    height: 100px;

    background: black;
  }
</style>

Now if you hit Run, you should see a "landscape”—which is really just a black box.

Let's put something on the landscape now. I used a picture of a tree, which I copied out of an old xkcd comic (thanks Randall Munroe!).

Example of this project with a Tree

But you should use something of your own choosing! Code is creative—think about what would go well in a storm, find it on google, and maybe use an image editor to make it into a black silhouette…

Example of this project with a Mystery Emoji

Drag/drop the image into your repl.it window to upload it into your project. Name the file something like "thing.png" or "thing.jpg" (whatever the original file was—probably .png or .jpg—use that).

Then add an image to your HTML (<img>), name it "thing", and:

<body>
  <div id="ground"></div>
  <img id="thing" src="thing.png" />
</body>

Then, add another block to your style for "thing". This one will look similar to the one we just added for our ground, but we’ll position it a little differently:

<style>
  /* ... */

  #thing {
    position: absolute;

    left: 0;
    bottom: 0;

    height: 100px;
    width: 100px;
  }
</style>

The End

That’s it y’all. You got yourself a storm.

Sources:

We'd love to see what you've made!

Share a link to your project (through Replit, GitHub etc.)