Haunted Stormy Night

Avatar ghosts crashed my house. Of course, there are much scarier things to be worried about this Halloween.
Avatar ghosts crashed my house. Of course, there are much scarier things to be worried about this Halloween.

Lightning strikes and rain pours down; strange creatures lurk around. They softly whisper, faces blue: “The man in the moon is watching you!” CSS filters and keyframe animations are just a few of the techniques used to create this spooky HTML scene. ?

Haunted Stormy Night

The Khan Academy avatars decided to haunt my house this Hallow’s Eve. I suppose that entails glowing pale blue and floating back and forth. (What else would a ghost do, anyway?)

The man in the moon, however, is a rather frightful fellow. He’ll bare his teeth about once in every five lightning strikes, and then some if you aren’t being wary. (Mwa-ha-ha-ha!)

If you’re feeling brave, you can summon extra lightning strikes by clicking anywhere on the screen.

Believe it or not, everything in this animation is an HTML element! The effects and animations are produced by CSS filters and CSS keyframe animations. The window and moon are HTML elements styled with CSS properties. JavaScript is used to randomize various aspects of the scene, such as the positions of ghosts, raindrops, and clouds. jQuery is the JavaScript framework used for DOM manipulation.

What follows are some notable techniques I used to create this spooky scene.

Staggered Entrances, Varying Speeds

At the beginning of the animation, all of the ghosts, clouds, and raindrops are hidden off screen. Some show up almost immediately, while others wait a while before entering.

It takes a while for all the particles to show up at the same time, and that is the work of the following JavaScript/jQuery code.

/* Add lots of .rain elements to the .window, with randomized horizontal positions, entry times and animation speeds. */
for(var i = 0; i < 70; i++){
    $("<div>")
        .addClass("rain")
        .css({
            "left": random(-10, 100) + "%",
            "animation-delay": random(18) + "s",
            "animation-duration": random(2.4, 3.2) + "s"
        })
        .appendTo($(".window"));
}

When the particles are generated, their animation-delay and animation-duration properties are initialized with random values. It can take anywhere from 0 to 18 seconds for a raindrop to start animating, and anywhere from 2.4 to 3.2 seconds for the raindrop to complete its fall. (Similar code is used when generating the ghosts and clouds.)

The staggered entrances as defined by the randomized animation-delay property help to keep ghosts and particles uniformly distributed throughout the screen. The randomized animation-duration ensures that their positions relative to each other are not constant throughout the animation; if all the raindrops completed their animation cycles at the same time, they would form the exact same pattern on the next animation cycle, which would look quite odd.

You can see the staggering and randomization described above more clearly in this modified animation. Ghosts are made more visible and won’t go off screen. Also, raindrops and clouds are not clipped inside the window area, so you can see where they are at the beginning of the animation.

Outside Or Inside The Window?

As you probably noticed from the modified animation above, the positions of the clouds and raindrops can sometimes move outside of boundaries of the window. How are they being constrained?

The way the HTML is structured, the moon, clouds, and raindrops are child elements of the window element.

<div class="window-wrapper">
    <div class="window">
        <div class="moon"></div>
        <div class="rain"></div>
        <div class="rain"></div>
        ...
        <div class="cloud"></div>
        <div class="cloud"></div>
        ...
    </div>
</div>

If not for the window’s overflow property being set to hidden, the rain and cloud particles would appear to be inside the house. As you saw, that totally ruins the illusion!

It's humid in here... Now, wait a minute!
It's humid in here... Now, wait a minute!

Lightning Flash

Two CSS properties are used to simulate a lightning flash effect: background-color and box-shadow.

During a lightning flash, the sky (or the window element’s background) turns white. The background color of the page also lightens.

The magical final touch is a huge white drop shadow applied to the window via its box-shadow property. This gives the window an electrifying glow whenever the lightning strikes.

@keyframes LightningShadowLargeWhite {
    10% { box-shadow: #fff 0 0 200px; }
}

Pale Blue Ghosts

The avatar images aren’t ghost-colored out of the box, but I can use a combination of CSS filters to make them so!

.ghosts {
    filter: sepia(1) saturate(2) hue-rotate(150deg)
}

These filters allow you to apply a blue tint to an element.

  • sepia(1) reduces the color palette to only use shades of brown. Sepia photo filters are commonly used to simulate the look of an old photograph.
  • saturate(2) boosts the image to make the colors appear more vivid and bright, hence the “pale” in pale blue ghosts.
  • hue-rotate(150deg) rotates the color wheel from the warm, orange-brown shades of the sepia filter to light blue.

You can change the degree value inside of hue-rotate() for a different tint color. By applying the sepia() and hue-rotate() filters together, you can tint an image using any color you want, regardless of what colors appear in the original image.

Simultaneous Animations, Two Layers of Opacity

The avatars, being ghosts for tonight, fade in and out of sight, so they are often quite difficult to see. They are more clearly illuminated, however, when lightning strikes.

The regular fading in and out and the lightning strike “illumination” run as two separate keyframe animations, and they both use the opacity property. How are they able to run simultaneously, without one interfering with the other?

The answer: one of the animations, Ghost, is applied to the ghost, and the other LightningOpacity, is applied to a common parent element of all ghosts, .ghosts.

<div class="ghosts">
    <div class="ghost-wrapper">
        <img class="ghost" src="...">
    </div>
    <div class="ghost-wrapper">
        <img class="ghost" src="...">
    </div>
    ...
</div>
body.lightning .ghosts {
    animation-name: LightningOpacity;
    animation-duration: 1.2s;
}
.ghost-wrapper {
    animation-name: Ghost, OscillateY;
    animation-iteration-count: infinite;
}
.ghost {
    animation-name: BackAndForth, OscillateRotate;
    animation-timing-function: linear, ease-in-out;
    animation-iteration-count: infinite;
}

A similar technique is used to allow the ghosts to both rotate back and forth and bob up and down at the same time. OscillateY is applied to the ghost’s parent element, .ghost-wrapper, while OscillateRotate is applied to the ghost image itself.

Add all of the above to BackAndForth animation, which uses the left property combined with absolute positioning, and each ghost will have up to 5 keyframe animations acting on it simultaneously at any given time. And they’re all staggered, and they vary in duration!

I’ll bet you all of my Halloween candy that no two runs of this animation will ever be alike.

Contest: Scary jQuery

This project was a winning entry to Khan Academy’s Scary jQuery contest, which ran from October 16th to November 2nd, 2015.

If you learned something new from this post, let me know in the comments! Have a safe, frightful Halloween!

And hope the man in the moon doesn't pay you a visit.
And hope the man in the moon doesn't pay you a visit.
By | 2016-10-29T11:49:20+00:00 October 28th, 2016|Tags: , |2 Comments

About the Author:

Darryl Yeo is a young programmer, web developer, computer graphics artist, musician, and composer who hopes to inspire others through his projects and inventions. Read more about Darryl.

Leave a Comment

2 Comments on "Haunted Stormy Night"

avatar
2000

Lucius Latham
Guest
October 29, 2016 11:36 am

Yeah, that’s really cool what you can do with CSS image effects. I remember your entry being particularly fascinating! Great blog post! ;)

Orange Juice Programmer
Guest
Orange Juice Programmer
January 3, 2018 2:21 pm

Apparently, the preview of this program doesn’t exactly work now after KA updated the editor..

wpDiscuz
Go to Top