New Project: Winternetizer, Web 2.snow

published:
2009.12.21
topics:
css
javascript

My latest project is a little "Web Two-Point-Snow" fun I put together as my personal holiday gift to the internet, or should I say winternet… winternet? Ok, I'll stop. Anyway, the Winternetizer adds festive, fancy, 100% Flash-free flakes of falling snow to any website.

You'll have a much better experience using a browser that Santa has on his Nice List such as Safari, Firefox 3.5+, or Chrome. In fact, in those browsers, I'd go so far as to say the snow looks so real you will find yourself flopping on the ground to make snow angels. Give it a try now before you read about the super cool technical details behind it:

(Incidentally, you can also add the JavaScript directly to your own site to delight your visitors. The Winternetizer project page has details.)

The Secrets to Better Snow

Yes, it is true that falling snow scripts go back probably over a decade. We've all seen it done using falling snowflake GIFs, or falling periods, or the trusty asterisk (which to be fair, is a great six-sided stand-in for an actual flake).

However, if you have a modern browser, you will have noticed that my falling snow script has depth-of-field blurring and parallax, flakes that rotate as they fall, and you lucky Mac users no doubt noticed the pretty actual snowflake graphics. The total effect is accomplished with text, CSS, and JavaScript — no Flash, no PNGs, no GIFs, no Canvas.

Secret #1: Unicode Snowflakes ❅

We can have sweet snowflake graphics without using any images thanks to Unicode text miscellaneous symbols! Can you see these snowflakes? ❄ ❅ ❆ They are selectable Unicode text! Windows users almost certainly don't have sweet Unicode character sets installed, and therefore my script falls back to the trusty cousin of the snowflake: the six-sided asterisk *.

Secret #2: Create Depth-of-Field Blur with CSS text-shadow

Herein lies the real fun and technical nerdery excitement of my falling snow! By using the CSS text-shadow property I am able to dynamically blur the snowflakes (be they awesome Unicode flakes or boring asterisks) to create the illusion of depth-of-field. Big flakes that are close to you are out of focus and blurry. Smaller flakes that are farther from you are in focus and sharp. Because we are blurring text, we don't have to create a bunch of pre-blurred PNGs with alpha transparency.

The technique I am using is just a variation on a technique often used for accessibility. I position the actual text off screen to the left with a negative text-indent and then offset the text-shadow by an equal positive amount so that the shadow of the text is visible on the screen but the original text is not.

Take a look at the following CSS and examples to help illustrate my point. First, here is a red asterisk and a blurry white asterisk shadow offset 3em to the right of the text:

*

p {
    color: #f00;
    text-shadow: 3em 0 5px #fff;
}

Now an example where the actual text is far off screen to the left and only the shadow remains visible:

Just a shadow of my former self.

p {
    text-indent: -999px;
    text-shadow: 999px 0 2px #fff;
}

Besides being able to arbitrarily blur my snowflakes using text-shadow, one of the other really happy accidents is that shadows of text aren't really in the DOM and don't receive click events or cover the things behind them. This means that you can click right through my snowflakes as if they weren't even there, so they don't interfere with the function of the page! Conversely, if I had animated my snowflakes inside of one giant Canvas, the entire page would be unusable since you would not be able to click anything that the Canvas covered up.

Secret #3: Ice/Frost the Cake with CSS transform

The final touch of fun for my falling snow is using the new CSS transformations to rotate some of the larger flakes as they fall for some added realism. I actually used my own CSS rotation jQuery patch to make this really easy on myself.

*
setInterval(
    function () {
        $('#example').animate({rotate: '+=5deg'}, 0);
    },
    200
);

UPDATE! Secret #4: Managing JavaScript Performance with Snowflake Throttling

Rendering each DOM node for every snowflake, plus blurring text, plus rotating each of the flakes eats up a lot of CPU. The Winternetizer attempts to render 50 flakes at 20 frames per second (fps), and then adds or removes flakes depending on how the page is performing.

The basic idea behind how I do this is capturing the number of milliseconds (ms) between each frame of animation. At 20fps there should be 50ms between each frame. If we're rocking along at 50ms smoothly each frame then we can probably add some snowflakes to make for a more captivating snow effect. However, if we're lagging behind at say 150ms per frame, then we really need to drop some snowflakes to speed things up.

So, first I capture the current time in milliseconds using new Date().getTime(), and then I setup an animation loop that draws each frame on a 50ms interval:

prevTime = new Date().getTime();
setInterval(move, 50);

Then, in my interval animation function I need the throttling code:

var newTime = new Date().getTime();
var diffTime = newTime - prevTime;
prevTime = newTime;

if (diffTime < 55 && flakeCount < absMax)
{
    // add a flake
}
else if (diffTime > 150)
{
	// remove a flake
}

The values of 55 and 150 are a bit arbitrary. One could dial in to whatever they think works. You'll also notice that I've included a maximum number of flakes that can be added… just so things never get out of control.

Ok, that's enough jabbering on. If you'd like you can just look at the original source code to see how I did everything.