Programming - - By Louis Simoneau

The WDS Title Sequence Explained: HTML5 and CSS3 in Action

I’ve just returned from Web Directions South in Sydney, where I saw some inspiring speakers geeking out on stage, and mingled with a bunch of other web-minded individuals. But the most inspiring thing I saw happened in the first ten minutes of the conference: Cameron Adams’s opening title sequence. At first sight, it’s just an animation revealing the names of all the speakers with some cool effects, played to a remix of the soundtrack to the film Inception. Then you learn that it’s created using HTML5, CSS3, and JavaScript in the browser.

Start by checking it out; you’ll have to view it using Chrome. For it to work correctly, you’ll need to adjust your screen resolution to 1024 by 768, load the page, switch to full-screen mode, and then refresh the page to see it from the start in the correct resolution.

Although it’s based on a soundtrack from Inception, Cameron’s demo doesn’t rely on the cast of characters explaining to us over three hours what’s going on. For that reason, I figured I’d dive into the source to gain a glimpse of the magic behind the scenes. The demo is made up of over 800 lines of CSS and 2,400 lines of JavaScript (not counting jQuery and a few monkey patches to add CSS3 support to it), so I’ll just be skimming the surface to explain a few of the cooler effects. If you’re more interested in a top-level overview of what was involved (and some of the weird bugs encountered along the way), check out Cameron’s blog post.

Music to Your Ears

The first thing you’ll notice when you view the source of the demo page is that there’s only one HTML element inside the <body> tags — an audio element:

<audio id="music" src="Music.mp3" oncanplaythrough="start();" preload="auto"></audio>

This, of course, is the new HTML5 element for native, in-browser audio.

The key here is the oncanplaythrough attribute. Like any on-something attribute, it refers to an event firing in the browser. In this case, the canplaythrough event fires when the element has loaded enough of the media file to be able to play it all the way to the end without buffering. Cameron uses it to call the start() method in his JavaScript to get his animation underway.

Timing is Everything

One of the trickiest aspects to an animation like this is the timing of various effects to the audio track. When those circles are pulsing around the first few names, they’re timed to coincide exactly with the haunting piano notes in the music.

How is this possible? Cameron’s JavaScript code begins with a big array of objects called TIME_CODE. Each object in the array contains a time code and a function name. The time code is either a number (in seconds from the beginning of the track), or a string like "+4", which just means “4 seconds after the last time code.” As you look through the array, you’ll see several time codes specified as multiples of a BEAT constant. That constant is the exact interval between the piano notes in the first few seconds of the music: 1.79 seconds.

The start() method, which is triggered as soon as the audio is ready to play (as we saw earlier), looks like this:


function start()
{
  $("body").unbind("click");

  stageWidth = $(window).width();
  stageHeight = $(window).height();

  startDate = new Date();

  var music = document.getElementById("music");
  music.play();

  setInterval(tick, 10);
}

The most important lines are the last two. First, he starts playing the music; then, he sets up an interval to run the tick method every ten milliseconds. The tick method is the cornerstone of the whole script:


function tick()
{
  var music = document.getElementById("music");

  if (TIME_CODE.length > 0)
  {
    var time = 0;

    if (typeof TIME_CODE[0].time == "string")
    {
      time = lastTime + parseFloat(TIME_CODE[0].time.replace(/+/, ""));
    }
    else
    {
      time = TIME_CODE[0].time;
    }

    if (music.currentTime >= time)
    {
      TIME_CODE[0].func();
      lastTime = time;
      TIME_CODE.splice(0, 1);
    }
  }
}

This is still fairly straightforward: the code grabs the first element in the TIME_CODE array; then checks to see whether the time property of that object is absolute or relative (that is, whether it’s a string or a number). If it’s relative, the code adds it on to the lastTime variable for the current time code. Then it gets the currentTime of the music; this is a built-in property of the HTML5 audio element. If the time code we’re waiting for has arrived, the code fires the function associated with it, sets lastTime, then chops out that first element of the TIME_CODE array using splice.

These allow the code to run each function at a very specific time, with a temporal resolution of ten milliseconds. As it turns out, this is close enough for our brains not to tell the difference, and the events on screen seem to be perfectly timed with the music.

Transitions and Transforms and Animations, Oh My!

The first function in TIME_CODE is presents(), but I’ll skip most of that one, because all it’s really doing is iterating over all the letters of the string “Web Directions Presents: South 2010” and fading them in and out. What’s interesting, though, is that the fading is taking place in CSS, not in the JavaScript. The JavaScript code is simply setting each letter’s opacity to either 0.9 or 0, which would normally look like letters flicking on or off. However, in the CSS you’ll see rules like this:

-webkit-transition: all 3s ease-in;

This means that whenever any property of the element targeted is changed (whether by JavaScript or in the CSS itself with a change of pseudo-class selector), it will be animated over the period of three seconds using the ease-in timing function. For more on timing functions and other available options, check out the W3C spec on the CSS transition module. The end result is that each letter will appear to fade smoothly in and out.

Now let’s take a look at a more interesting effect: those pulsing circles I was talking about earlier. Each circle is actually a div with a border-radius set to half its size. Those pulses of light that emanate from each circle are simply identical divs with some CSS animation applied. Here’s the relevant CSS:


._1pulse
{
  position: absolute;
  background: -webkit-gradient(radial, 50% 50%, 0, 50% 50%, 30, from(rgba(255,255,255,0)), to(rgba(255,255,255,1)));
  opacity: 0.4;
  -webkit-animation-name: _1pulseAnim;
  -webkit-animation-duration: 1.85s;
  -webkit-animation-timing-function: ease-out;
}

@-webkit-keyframes _1pulseAnim
{
  0% {opacity: 0.4; -webkit-transform: scale(1)}
  100% {opacity: 0; -webkit-transform: scale(5)}
}

That code contains two separate bits of CSS3 magic. First of all, the pulse has a background radial gradient applied with -webkit-gradient. Second, and most interesting, is the animation. CSS3 animations require first setting up a number of keyframes using the @keyframes declaration (or in this case, @-webkit-keyframes). The set of keyframes is given a name (_1pulseAnim), which can then be referred to from a CSS rule. Each keyframe is given a selector: a percentage that tells the browser which point in the animation the keyframe sits at. In this very simple case, Cameron has just two keyframes: one for the initial state (0%), and one for the final state (100%). Only two properties are animated: opacity and scale. Those keyframes are then assigned to a given element’s animation by including them in a -webkit-animation-name declaration, like the one in the code above.

The end result is that when the pulse div is appended to the document, the animation runs. It scales up to five times the size of the central circle while fading away.

For His Next Trick …

There are tons more animations in this page: a network of lines stretching between those circles, followed by a scrolling wall of letters that pauses to highlight other presenters’ names. I don’t have space here to go over how each is done, but feel free to dive into the code yourself if you’re curious. However, I do want to briefly touch on what is, for me, the most impressive animation: the white boxes that swing around the screen to reveal names behind them. Believe it or not, almost this entire animation is performed by adding a single class to a single div. Cameron first creates all the names, and then sets a timeout to fade each one in at a given time. He then takes a giant div containing all the rounded rectangular boxes, and adds the class fly:


$("#_13blocks").addClass("fly")

This innocuous line of code triggers the following CSS animation:


#_13blocks.fly
{
  -webkit-animation-name: fly, flyOrigin;
  -webkit-animation-duration: 19.7s, 19.7s;
  -webkit-animation-timing-function: ease-in-out, ease-in-out;
  -webkit-animation-fill-mode: forwards, forwards;
}

@-webkit-keyframes fly
{
  0% {-webkit-transform: rotate(0deg) scale(1);}
  8.3% {-webkit-transform: rotate(-90deg) scale(3);}
  16.6% {-webkit-transform: rotate(-180deg) scale(4.3);}
  24.9% {-webkit-transform: rotate(-270deg) scale(5.6);}
  33.2% {-webkit-transform: rotate(-360deg) scale(7.5);}
  41.5% {-webkit-transform: rotate(-450deg) scale(10);}
  49.8% {-webkit-transform: rotate(-540deg) scale(13);}
  51.5% {-webkit-transform: rotate(-540deg) scale(13);}
  58.1% {-webkit-transform: rotate(-630deg) scale(13);}
  59.6% {-webkit-transform: rotate(-630deg) scale(13);}
  66.4% {-webkit-transform: rotate(-720deg) scale(13);}
  67.9% {-webkit-transform: rotate(-720deg) scale(13);}
  74.7% {-webkit-transform: rotate(-810deg) scale(13);}
  76.2% {-webkit-transform: rotate(-810deg) scale(13);}
  95.0% {-webkit-transform: rotate(-990deg) scale(1);}
  96.0% {-webkit-transform: rotate(-990deg) scale(1); -webkit-animation-timing-function: ease-in;}
  100% {-webkit-transform: rotate(-990deg) scale(15);}
}

Each keyframe specifies a rotation and a scale (most of which are very high in order to make the div several times larger than the browser’s viewport). The result: the div flies around the screen, stopping with a white area aligned to display a line of text. Each line will fade in at exactly that moment, thanks to the timeouts specified earlier in the JavaScript. The container then swings round again to display the next name. The ease-in-out timing function makes the animation seem smooth and natural, with a little bit of bounce. The overall effect is a sweeping demonstration of the power of CSS3 animations.

Haters Gonna Hate

The naysayers among you will now be crying out, “This only works at a specific resolution in a specific browser, and uses a ton of vendor prefixes! It’s useless in the real world!” The point is that it works in the context it was designed for: it played in the browser, projected onto the screen, and blew us all away. It’s not even close to the easiest way of creating that animation, but it does serve to show us where we’re going, and what’s just around the corner for web developers. With IE9 supporting a number of CSS3 features (though unfortunately not the animations that created this demo), the browser wars are back on, except that this time, they’re being fought over standards.

I’ve barely touched on a third of the cool animations and effects in the demo, so be sure to pop open the source and have a play if you really want to understand what’s going on.

Hats off to Cameron for the amazing work. I can’t even imagine how much work was involved in painstakingly lining up all those elements, not to mention the audio, to create such a seamless sequence. I’d also like to thank Jetstar who, by delaying my flight, enabled me to spend a few hours sitting on the floor of the departure lounge reading all that code.

Sponsors