Use JavaScript to chain animations with CSS
Animating with CSS is taking off. Peter Gasston shows how we can extend its capabilities by mixing and chaining with JavaScript events
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.
- Knowledge needed: Intermediate CSS, basic HTML, intermediate JavaScript
- Requires: jQuery, Modernizr, modern web browser
- Project time: 1-2 hours
- Download source files
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:
- <div class="holder">
- <img class="img-1" src="kitten-1.jpg">
- <img class="img-2" src="kitten-2.jpg">
- </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:
- <ol id="trigger">
- <li data-img="kitten-1" class="active">1</li>
- <li data-img="kitten-2">2</li>
- <li data-img="kitten-3">3</li>
- <li data-img="kitten-4">4</li>
- </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.
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:
- .holder { position: relative; }
- .holder img {
- bottom: 0;
- left: 0;
- position: absolute;
- }
- 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:
Get top Black Friday deals sent straight to your inbox: Sign up now!
We curate the best offers on creative kit and give our expert recommendations to save you time this Black Friday. Upgrade your setup for less with Creative Bloq.
- img.img-2 {
- transform: scale(0.96) translateY(-3.6%);
- transition: all 500ms ease-out;
- }
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:
- @keyframes upndown {
- 50% { transform: scale(1) translateY(-120%); }
- to {
- transform: none;
- z-index: 9;
- }
- }
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:
- 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.
The script
In my document I’m using three JavaScript files:
- <script src="jquery-1.7.2.min.js"></script>
- <script src="modernizr.js"></script>
- <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.
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:
- $(document).ready(function(){
- if(Modernizr.cssanimations && Modernizr.csstransitions) {
- // Run our script
- } else {
- // Do something else
- });
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):
- var holder = $('.holder');
- 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:
- 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:
- $('#trigger').on('click','li:not(.active)',swapStage1);
Here’s the whole of the swapStage1 function:
- function swapStage1(e) {
- $(e.currentTarget).addClass('active').siblings().removeClass('active');
- var img2data = $(e.currentTarget).data('img');
- holder.find('img.img-2').attr('src',img2data + '.jpg').addClass('stage-1');
- }
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.
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:
- 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:
- 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:
- function swapStage2(e) {
- var screen1 = holder.find('img.img-1');
- var screen2 = holder.find('img.img-2');
- screen1.attr('class','img-2 stage-2');
- screen2.attr('class','img-1');
- $('#trigger li.active').removeClass('rotate');
- }
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:
- 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:
- function swapStage3(e) {
- var screen1 = holder.find('img.img-1');
- var screen2 = holder.find('img.img-2');
- screen2.removeClass('stage-2').detach();
- screen1.after(screen2);
- }
But we’re still not quite done. There’s one more animation event listener in our script:
- 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.
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.
Thank you for reading 5 articles this month* Join now for unlimited access
Enjoy your first month for just £1 / $1 / €1
*Read 5 free articles per month without a subscription
Join now for unlimited access
Try first month for just £1 / $1 / €1
The Creative Bloq team is made up of a group of design fans, and has changed and evolved since Creative Bloq began back in 2012. The current website team consists of eight full-time members of staff: Editor Georgia Coggan, Deputy Editor Rosie Hilder, Ecommerce Editor Beren Neale, Senior News Editor Daniel Piper, Editor, Digital Art and 3D Ian Dean, Tech Reviews Editor Erlingur Einarsson and Ecommerce Writer Beth Nicholls and Staff Writer Natalie Fear, as well as a roster of freelancers from around the world. The 3D World and ImagineFX magazine teams also pitch in, ensuring that content from 3D World and ImagineFX is represented on Creative Bloq.