Chain CSS animations together with JavaScript

  • Knowledge needed: Intermediate CSS, basic HTML, intermediate JavaScript
  • Requires: jQuery, Modernizr, modern web browser
  • Project time: 1-2 hours
  • Download source files

This article first appeared in issue 229 of .net magazine – the world's best-selling magazine for web designers and developers.

It’s been four and a half years since CSS transitions were first introduced in WebKit, and three have passed since they were joined by CSS keyframe animations. Both are now approaching the tipping point of browser implementation – they are supported in Chrome, Safari and Firefox, meaning that they’re already available to some 50 to 60 per cent of web users, and will soon be in Opera 12 and Internet Explorer 10. This means they’re ready for the spotlight.

Using CSS animations and transitions, henceforth known as CSS animation, has already been well covered (not least in .net), so in this tutorial I’m going to show you how CSS animations can be chained together using JavaScript event handlers, which really increases their potential. I’m going to do it by building a very simple, stacked-card carousel kind of thing – which flips between a set of chosen images.

Internet Explorer 10 is due for release later this year, which should provide a big boost to the uptake of CSS animation

Let me be clear from the outset: the carousel is a MacGuffin; it’s a plot device; it’s what I’m using to show you how to combine CSS animation and JavaScript – but it’s not the point of the exercise. There are doubtless better ways to build a carousel that does this, but those ways aren’t going to teach you about animation events.

Anyway, if you’d like to follow along or take a look at the finished example, all of the files are in the support files.

The markup

The core markup I’m using is very simple; it’s a container div with two img element children, each with a unique class:

  1. <div class="holder">
  2. <img class="img-1" src="kitten-1.jpg">
  3. <img class="img-2" src="kitten-2.jpg">
  4. </div>

For the controls below the carousel I’m using an ordered list with four items, each of which has a data attribute whose value is the name of one of the four image files I’ll be using in my example:

  1. <ol id="trigger">
  2. <li data-img="kitten-1" class="active">1</li>
  3. <li data-img="kitten-2">2</li>
  4. <li data-img="kitten-3">3</li>
  5. <li data-img="kitten-4">4</li>
  6. </ol>

You’ll notice that this markup isn’t in the HTML file – I’m going to add it with script later to aid with graceful degradation. These two blocks of markup contain all of the information that I'll need to create my script, but first we should style them.

CSS transitions were introduced back in October 2007 and announced on WebKit’s Surfin’ Safari blog

The styles

To begin, the two img elements are absolutely positioned at the same point inside their parent, and .img-1 is given a higher z-index so that it appears in front of .img-2:

  1. .holder { position: relative; }
  2. .holder img {
  3. bottom: 0;
  4. left: 0;
  5. position: absolute;
  6. }
  7. img.img-1 { z-index: 8; } img.img-2 { z-index: 7; }

To make the img elements appear stacked, .img-2 has a CSS transform applied to it to scale it down a little, and move it slightly above .img-1. It also has a transition applied to it, which I’ll explain in due course:

  1. img.img-2 {
  2. transform: scale(0.96) translateY(-3.6%);
  3. transition: all 500ms ease-out;
  4. }

A quick word of advice: don’t forget to use all of the relevant vendor prefixes on those properties.

Finally, I’m going to create a keyframe animation called upndown that will make .img-2 slide up from behind .img-1, and down in front of it:

  1. @keyframes upndown {
  2. 50% { transform: scale(1) translateY(-120%); }
  3. to {
  4. transform: none;
  5. z-index: 9;
  6. }
  7. }

You can see that from its starting point it will move up 120% and back down, increasing the z-index to 9 as it does so. Once we’ve created the keyframes, we call them on .img-2, but only when the extra class stage-1 is applied to it:

  1. img.img-2.stage-1 { animation: upndown 1s forwards; }

If you look in the example stylesheet, netmag.css, you’ll notice plenty of other rules – but those are mostly for presentation and aren’t directly relevant to this tutorial. So now that we’ve written the markup and CSS, let’s move on and take a look at the JavaScript.

Opera 12 features support for CSS animations, which means a new window of opportunity on mobile devices

The script

In my document I’m using three JavaScript files:

  1. <script src="jquery-1.7.2.min.js"></script>
  2. <script src="modernizr.js"></script>
  3. <script src="netmag.js"></script>

The first, jQuery, I shouldn’t need to explain. The next file is a custom build of Modernizr, which detects support for CSS animations and transitions. The third file is where I’m putting my own script, and it’s this that I’ll be talking about.

Modernizr assesses browser capabilities regarding CSS animations and transitions

The first step is to initiate jQuery with the ready() method and check whether the browser is capable of running this demo by using Modernizr to test for support:

  1. $(document).ready(function(){
  2. if(Modernizr.cssanimations && Modernizr.csstransitions) {
  3. // Run our script
  4. } else {
  5. // Do something else
  6. });

The next thing I’m going to do is to set up a couple of variables: holder is the DOM of our container element, .holder, while controls contains all of the markup for the controls that I demonstrated earlier in the tutorial (in ‘The markup’ section):

  1. var holder = $('.holder');
  2. var controls = '<ol id="trigger">[etc]</ol>';

After my variables I go on to define three functions: swapStage1, swapStage2 and swapStage3 – I’ll come to each of these in turn. With all of my global variables and functions defined, I insert the controls markup after the container element:

  1. holder.after(controls);

And then it’s time to set up my first event listener, which will call the function swapStage1 when any control list item is clicked on, unless it has the class active applied to it:

  1. $('#trigger').on('click','li:not(.active)',swapStage1);

Here’s the whole of the swapStage1 function:

  1. function swapStage1(e) {
  2. $(e.currentTarget).addClass('active').siblings().removeClass('active');
  3. var img2data = $(e.currentTarget).data('img');
  4. holder.find('img.img-2').attr('src',img2data + '.jpg').addClass('stage-1');
  5. }

First, this adds the class active to only the clicked list item, which introduces a highlighting style but more importantly prevents it from being clicked again and causing any script conflicts. It then gets the data-img attribute value from the clicked item and changes the href attribute of .img-2 to use this new value; this is what changes the image used. Finally, it adds the class stage-1 to .img-2, and this is where our CSS animations events come into play.

I based this tutorial’s example on a widget I made for Top 10, which you can see here

CSS animations events

When .img-2 has the class stage-1 added, it begins the keyframe animation called upndown. This, you’ll remember, raises the element up, then lowers it in front of .img-1. When this animation has completed, it fires an event called animationend, and in our script there’s an event handler set up to listen for this:

  1. holder.on('animationend','img.img-2.stage-1',swapStage2);

In actual fact the script uses four different values in the event listener, because to date only Firefox has implemented this without a prefix:

  1. holder.on('animationend MSAnimationend oanimationend webkitAnimationEnd', 'img.img-2.stage-1', swapStage2);

Regardless, what this does is call the function swapStage2 when the animation upndown has completed – so you can see immediately that this is a very useful way of chaining animations together.

In swapStage2 the script swaps the classes of the elements: .img-1 becomes .img-2 and vice versa – and also adds the class stage-2 to the newly defined .img-2. Here’s the function in full:

  1. function swapStage2(e) {
  2. var screen1 = holder.find('img.img-1');
  3. var screen2 = holder.find('img.img-2');
  4. screen1.attr('class','img-2 stage-2');
  5. screen2.attr('class','img-1');
  6. $('#trigger').removeClass('rotate');
  7. }

Just ignore that last rule; I’ll come to that soon. What we’re interested in is the swap of classes – this is required because the img elements have swapped position – and the addition of stage-2 to .img-2. Remember that when we set up our CSS we had a transition applied to .img-2, and as .img-1 becomes .img-2 this transition occurs as the element moves into its stacked position. Once this has finished, another event is fired – for which we have a listener primed:

  1. holder.on('transitionend','img.img-2.stage-2', swapStage3);

This works in the same way as animationend, but on the CSS transition; so when the element has moved back into the stack, the transition completes and the event is fired, calling function swapStage3. Again, you can see how chaining different types of CSS animation becomes easy.

The swapStage3 function tidies up: it removes .img-2 from the DOM and inserts it after .img-2, restoring the original DOM order so as to avoid conflicts:

  1. function swapStage3(e) {
  2. var screen1 = holder.find('img.img-1');
  3. var screen2 = holder.find('img.img-2');
  4. screen2.removeClass('stage-2').detach();
  5. screen1.after(screen2);
  6. }

But we’re still not quite done. There’s one more animation event listener in our script:

  1. holder.on('animationstart','img.img-2.stage-1',rotate);

The animationstart event is fired, as you can probably guess, when the upndown animation starts, rather than when it ends. In our script the listener is used to call the function rotate, which adds a class to the active control that triggers a keyframe at-rule. This spins it while the main animation takes place; it’s removed when the animation ends, as we saw earlier in function swapStage2. This spinning effect is an extra touch and not at all required, but is a good way for me to demonstrate animationstart.

If you want some great examples of what's possible with CSS animation, take a look at Lea Verou's Animatable gallery

Summing up

So that’s it; as I said at the start, I’m sure there are better ways to build a stack flipper carousel than this one. But it’s a good method of demonstrating CSS animation events, which are extremely useful for chaining together strings of functions and animations, as I’ve done here in a very simple way.