Partager via


Introduction to CSS3 Animations

TestAnimations

Today’s HTML5 applications can provide awesome experiences thanks to the new CSS3 specifications. One of them is CSS3 Animations. It can help you building rich animations on HTML elements. This can provide interesting feedbacks to the users and enables fast & fluid UIs. As those new animations are most of time hardware accelerated by the GPU, they definitely raise up the quality bar of the new generation of HTML5 applications.

According to the “CSS Animation Module Level 3” specification on the W3C site, CSS3 Animations introduces defined animations, which specify the values that CSS properties will take over a given time interval. This specification is an extension to CSS Transitions.

As CSS3 Animation is an extension to CSS3 Transitions, you should first read the article of my colleague David Catuhe on Transitions here: Introduction to CSS3 Transitions.

We’ll see in this article an interesting demo highlighting the potential of CSS3 animations, how to build simple animations & how to handle fallback in JavaScript:

  1. CSS3 Animations
  2. Browsers Support
  3. CSS3 Animations JavaScript fallback library
  4. Conclusion

Let’s first start by quickly demonstrating what CSS3 Animations are. Here is a sample animation of a StarWars AT-AT which uses CSS3 Animations to animate parts of the transport (and which will fall back to JavaScript if your browser doesn’t support CSS3 Animations):

You can test this sample also in a separate window here: https://david.blob.core.windows.net/html5/css3atat/index.htm

Note: this sample has been tested successfully with native animations under IE10/IE10 Mobile (Windows Phone 8), Chrome, Firefox 16+, Opera 12.1 & iPad 2 and with a JS fallback under IE9 desktop & mobile (Windows Phone 7.5). This article has been updated on the 14/12/2012 to remove the –ms and –moz prefixes as IE10+, Firefox 16+ & Opera 12.1+ are now supporting the unprefixed versions of transitions, animations and transforms! The list of unprefixed features are described here for IE10: Moving the Stable Web Forward in IE10 . Chrome 25 should also drop prefixes.

This sample is based on the awesome work done by Anthony Calzadilla. You can check other incredible demos on his website here: https://www.anthonycalzadilla.com . I’m a huge fan of the I twitty the fool sample using SVG & CSS3 Animation for instance. Clignement d'œil

CSS3 Animations

Introduction

Let’s first review on what you can play to build the animations. CSS3 Animations works basically on the same values as CSS3 Transition.

Here they are: click here to display/hide them

  • color: interpolated via red, green, blue and alpha components (treating each as a number, see below)
  • length: interpolated as real numbers.
  • percentage: interpolated as real numbers.
  • integer: interpolated via discrete steps (whole numbers). The interpolation happens in real number space and is converted to an integer using floor().
  • number: interpolated as real (floating point) numbers.
  • transform list: see CSS Transforms specification: https://www.w3.org/TR/css3-2d-transforms/
  • rectangle: interpolated via the x, y, width and height components (treating each as a number).
  • visibility: interpolated via a discrete step. The interpolation happens in real number space between 0 and 1, where 1 is "visible" and all other values are "hidden".
  • shadow: interpolated via the color, x, y and blur components (treating them as color and numbers where appropriate). In the case where there are lists of shadows, the shorter list is padded at the end with shadows whose color is transparent and all lengths (x, y, blur) are 0.
  • gradient: interpolated via the positions and colors of each stop. They must have the same type (radial or linear) and same number of stops in order to be animated.
  • paint server (SVG): interpolation is only supported between: gradient to gradient and color to color. They then work as above.
  • space-separated list of above: If the lists have the same number of items, each item in the list is interpolated using the rules above. Otherwise, no interpolation.
  • a shorthand property: If all the parts of a shorthand can be animated, then interpolation is performed as if each property was individually specified.

And the following properties must be supported for animations: click here to display/hide them

  • background-color (color)
  • background-image (only gradients)
  • background-position (percentage and length)
  • border-bottom-color (color)
  • border-bottom-width (length)
  • border-color (color)
  • border-left-color (color)
  • border-left-width (length)
  • border-right-color (color)
  • border-right-width (length)
  • border-spacing (length)
  • border-top-color (color)
  • border-top-width (length)
  • border-width (length)
  • bottom (length and percentage)
  • color (color)
  • crop (rectangle)
  • font-size (length and percentage)
  • font-weight (number)
  • grid-* (various)
  • height (length and percentage)
  • left (length and percentage)
  • letter-spacing (length)
  • line-height (number, length and percentage)
  • margin-bottom (length)
  • margin-left (length)
  • margin-right (length)
  • margin-top (length)
  • max-height (length and percentage)
  • max-width (length and percentage)
  • min-height (length and percentage)
  • min-width (length and percentage)
  • opacity (number)
  • outline-color (color)
  • outline-offset (integer)
  • outline-width (length)
  • padding-bottom (length)
  • padding-left (length)
  • padding-right (length)
  • padding-top (length)
  • right (length and percentage)
  • text-indent (length and percentage)
  • text-shadow (shadow)
  • top (length and percentage)
  • vertical-align (keywords, length and percentage)
  • visibility (visibility)
  • width (length and percentage)
  • word-spacing (length and percentage)
  • z-index (integer)
  • zoom (number)

SVG

The properties of SVG objects are animatable when they are defined as animatable:true in the SVG specification: https://www.w3.org/TR/SVG/struct.html. But at the time where this article is written, I didn’t manage to combine CSS3 Animation directly on SVG elements in any of the latest browsers versions. Today’s samples on the web are then doing a little trick: they are embedding SVG resources into different DIV animated by CSS3 like the I twitty the fool sample.

Declarations

To declare an animation in a CSS file, here is the kind of generic code you’ll need to write:

 @keyframes name_of_the_animation {
  from {
   property_to_animate: initial_value;
  }
  50% {
     property_to_animate: intermediate_value;
  }
  to {
    property_to_animate: final_value;
  }
}

Which could also be written like that:

 @keyframes name_of_the_animation {
  0% {
   property_to_animate: initial_value;
  }
  50% {
     property_to_animate: intermediate_value;
  }
  100% {
    property_to_animate: final_value;
  }
}

This animation definition declares 3 steps 0, 50 & 100%. You should at least set a from (or 0%) and a to (or 100%) steps to build a correct animation (minimum 2 steps thus). Once done, you may add as many keyframes as you’d like between 0 and 100% to handle precisely the various steps of your animations.

Once the definition declared, you can affect it to an element using the classical CSS3 selectors and you’ll need also to configure the animation options. Here the kind of generic blocks you’ll see:

 #id_of_the_html_element {
    animation-name: name_of_the_animation;
    animation-duration: number_of_seconds s;
    animation-iteration-count: number | infinite;
}

To better understand, let’s review a real sample. Let’s now see how the head of our AT-AT is moving.

Here’s the animation declaration:

 @keyframes rotate-skull {
    0% {
        transform: rotate(0deg)
    }
    25% {
        transform: rotate(15deg)
    }
    50% {
        transform: rotate(-5deg)
    }
    55% {
        transform: rotate(0deg)
    }
    75% {
        transform: rotate(-10deg)
    }
    100% {
        transform: rotate(0deg)
    }
} 

We’ve got 6 steps (0, 25, 50, 55, 75 & 100%) working on the CSS3 2D transform attributes by changing the value of the rotation.

The animation is then applied via this CSS rule:

 #skull 
{
    animation-name: rotate-skull;
    animation-duration: 7s;
    animation-iteration-count: infinite;       
}

We’re targeting the <div> element having the “id=skull” and we’re applying the animation named “rotate-skull” on it. The animation will have to be completed in 7s and be played an infinite number of times.

Note: you need to copy/paste these blocks to use –webkit-keyframes/-webkit-transform/–webkit-animation in order to make this code works in Chrome/Safari as they’re not supporting the final unprefixed specification yet.

Here is the living result if your browser supports CSS3 Animations:

We could have written this rule in a shorter manner using the animation shorthand property:

 #skull {
    animation: rotate-skull 7s infinite;
}

The animations will be triggered as soon as a matching rule is applied. You can then play or stop animations simply via JavaScript or via CSS3 to play with the classes affected to a tag.

Non-linear animations

The “animation-timing-function” property can be used if you want non-linear animations. You can even mix the type of timing functions during each keyframe.

Basically, CSS3 animations will use cubic bezier curve to smooth the animation by computing different speed over its duration.

The following functions are supported:

  • linear: Constant speed
  • cubic-bezier: Speed will be computed according to a cubic bezier curve define by two control points : P0 and P1 (so you will have to define 4 values here : P0x, P0y and P1x, P1y.
  • ease: Speed will be computed with cubic-bezier(0.25, 0.1, 0.25, 1)
  • ease-in: Speed will be computed with cubic-bezier(0.42, 0, 1, 1)
  • ease-inout: Speed will be computed with cubic-bezier(0.42, 0, 0.58, 1)
  • ease-out: Speed will be computed with cubic-bezier(0, 0, 0.58, 1)

Here is a simulation tool written by David Catuhe that uses pure JavaScript to show the impact of each timing function:

Note: this tool uses in-line SVG supported by Firefox, Chrome, Opera 11.60+ & IE9/10. It won’t work properly under Opera 11.50 & Safari on iPad thus.

This is an awesome tool using SVG. You can even play with your mouse on the custom function to edit the curve. If you’d like to know more about this tool, please again have a look to David’s article.

If your browser supports CSS3 animations, let’s now see a simple demo using easing functions to animate a canvas tag containing an animated sprite with CSS3.

Here is the CSS3 animations code that will be used in this demo:

 @keyframes demo {
    from {
      animation-timing-function: ease;
      transform: translateX(0px);
    }
    50% {
      animation-timing-function: ease-in;
      transform: translateX(300px);
    }
    to {
      animation-timing-function: ease-inout;
      transform: translateX(900px);
    }
}

#testCanvas
{
    animation-delay: 0s;
    animation-duration: 6s;
    animation-iteration-count: infinite;
    animation-name: demo;
}

As well as all the vendor prefixes variations to make it works also in Google Chrome & Opera. And here’s the living output:

iframe frameborder="0" height="90" src="https://david.blob.core.windows.net/html5/css3animcanvas/sprites.htm" width="1000">

If your browser doesn’t support CSS3 Animation but support canvas, the sprite’s running animation should be displayed but the character won’t move through the width of the screen.

Note: if you’d like to know more about canvas and sprites animation, you can have a look to this article: HTML5 Gaming: animating sprites in Canvas with EaselJS

Delay

The “animation-delay” property simply allows an animation to begin execution some time after it is applied.

Events

3 events could be raised during an animation. They are named “animationstart”, “animationend” and “animationiteration”. Depending on your browser, the correct name will be for instance:

  • IE10+, Firefox 16+ & Opera 12.1+: animationend
  • Chrome/Safari: webkitAnimationEnd

The event will give you the following details:

  • animationName: name of the animation which raised the event
  • elapsedTime: the amount of time the animation has been running, in seconds

Here is an usage sample for IE10, Firefox & Opera which are using the standard unprefixed version:

 elementToAnimate.addEventListener("animationend", function () {
    alert("the end !");
}, false);

More about CSS3 animations

CSS3 animations are really useful for 2 main reasons:

  • Hardware acceleration: CSS3 Animations are most of the time directly handled by the GPU and could produce smoother results. This could then be a very interesting approach for mobile devices.
  • Better separation between code and design: I know there is some debates on this point but with David, we think that a developer shouldn’t be aware of animations or anything related to design as much as possible. In the same way the designer/artist must not be aware of JavaScript. CSS3 offers then this possibility and could let the designers work with their classical tools to generate the appropriate animations on elements, between screens, etc.

To highlight this importance in performance, the following HTML5 game I wrote using a full frame <canvas>: HTML5 Platformer run at 60 fps in IE9/IE10 on my PC but at 10 fps max on some tablets. This is because its CPU is much more limited and some tablets are currently not hardware-accelerating <canvas>. Using CSS3 Transitions/Animations to animate several smaller <canvas> elements could provide a huge performance boost for this game. Think about it when you’re targeting mobile devices!

Browsers Support

As you can see on the following report produced by caniuse.com, the CSS3 animations are now supported on a wide range of browsers:

image

But you still need to use the vendor’s prefix –webkit- to make a cross-browsers compatible application.

But the question could be: how to handle browsers that don’t support this new feature?

First option is to just do nothing. Thanks to the beauty of graceful degradation, you could just let the user only see a static image if you’ve worked correctly. This is for instance the case of these 2 original samples of Anthony: I Twitty the Fool! and Pure CSS3 AT-AT Walker . When watched in IE9, it looks like we only have a static image. When watched in IE10, the very same code shows nice animations. IE10 users will then have an enhanced version while IE9 will still be able to view and use properly the website. The more modern your browser is, the more visual bonus you will have. 

The second option is to detect the feature via a JS library like Modernizr and try to offer the same animation via a JavaScript library that will mimic the animations. This is what we usually call a fallback mechanism. Unfortunately, I haven’t found today a working & complete JS library that could replace CSS3 animations when not supported by the browser.

I have then written a sample JS library more or less specifically designed for the AT-AT sample.

CSS3 Animations JavaScript fallback library

Animations are nothing more than a series of transitions separated by a certain duration defined via the keyframes. I’ve then reused the concepts built by David Catuhe in his transitions helper library. I let you reviewing his article to check the base of the concepts behind the code.

On my side, I’ve added some support to animate the CSS3 2D Transform rotation & translation values and a way to iterate through the keyframes.

Here is the main part of the library you need to review:

 // Animation object
// It need the HTML targeted element, the name of the animation, its duration & iteration count and
// the keyframes contained in an array object
// View the animation simply as a sequence of transitions played a certain number of times
ANIMATIONSHELPER.animation = function (target, name, duration, iterationcount, keyframes) {
    // saving the properties values
    this.name = name;
    this.duration = duration;
    this.iterationcount = iterationcount;
    this.target = target;

    var elapsedtime = 0;
    var keyframeduration = 0;
    var elapsedtime = 0;

    // Transforming the percentage of each keyframe into duration value
    for (var i = 0; i < keyframes.length; i++) {
        keyframeduration = ((keyframes[i].percentage * duration) / 100) - elapsedtime;
        keyframes[i].duration = keyframeduration;
        elapsedtime += keyframeduration;
    }

    this.currentTransition = { isPlaying: false };
    this.keyframes = keyframes;
    this.keyframesCount = keyframes.length;
    this.currentKeyFrameIndex = 0;

    // The nextTransition() function return the next transition to run
    // based on the current keyframe to play
    this.nextTransition = function (keyframe, ease, customEaseP1X, customEaseP1Y, customEaseP2X, customEaseP2Y) {
        var properties = [];
        var finalValues = [];
        var transition;

        // Compared to the original TRANSITIONSHELPER of David Catuhe
        // We need a specific code to play with the CSS3 2D Transform properties values
        if (keyframe.propertyToAnimate === "transform") {
            for (var i = 0; i < keyframe.transformType.length; i++) {
                properties.push(keyframe.transformType[i].type);
                if (keyframe.transformType[i].type == "rotate") {
                    finalValues.push({ deg: keyframe.transformType[i].value1 });
                }
                else {
                    finalValues.push({ x: keyframe.transformType[i].value1, y: keyframe.transformType[i].value2 });
                }
            }

            // Create a new transition
            transition = {
                name: this.name + this.currentKeyFrameIndex,
                target: this.target,
                properties: properties,
                finalValues: finalValues,
                originalValues: ANIMATIONSHELPER.extractValues(target.style[ANIMATIONSHELPER.currentTransformProperty], this.name),
                duration: keyframe.duration,
                startDate: (new Date).getTime(),
                currentDate: (new Date).getTime(),
                ease: ease,
                customEaseP1X: customEaseP1X,
                customEaseP2X: customEaseP2X,
                customEaseP1Y: customEaseP1Y,
                customEaseP2Y: customEaseP2Y,
                isPlaying: true,
                type: "transform"
            };

            return transition;
        }
        // If it's a classic property to animate, we're using more or less the TRANSITIONSHELPER as-is
        else {
            return TRANSITIONSHELPER.transition(this.target, keyframe.propertyToAnimate, keyframe.value, keyframe.duration, TRANSITIONSHELPER.easingFunctions.linear);
        }
    };

    // each animation object has a tick function
    // that will be called every 17 ms (to target 60 fps)
    // This ticker is monitoring the current state of the transition and
    // create a new transition as soon as the old one is finished/dead
    this.tick = function () {
        if (this.iterationcount > 0) {
            if (!this.currentTransition.isPlaying) {
                this.currentTransition = this.nextTransition(this.keyframes[this.currentKeyFrameIndex], ANIMATIONSHELPER.easingFunctions.linear);
                // We're using our own global ticker only for the 2D transformations
                // Otherwise, we're using the one from the TRANSITIONSHELPER library
                if (this.currentTransition.type === "transform") {
                    ANIMATIONSHELPER.currentTransitions.push(this.currentTransition);
                }
                this.currentKeyFrameIndex++;

                // We've reached the last keyframe (100%). We're starting back from the beginning
                if (this.currentKeyFrameIndex >= this.keyframesCount) {
                    this.currentKeyFrameIndex = 0;
                    this.iterationcount--;
                }
            }
        }
    };
};

The first part of the code is iterating through each keyframe to compute the exact duration specified by each percentage. We’re then defining a nextTransition() function that will dynamically build the next transition to play based on the current index into the keyframes collection. At last, we’ve got a tick() function that will monitor the current state of the transition applied. Once the transition is finished or dead, it asks for the next transition, push it to the stack of transitions to be played and moves the indexes.

This tick() function is called thanks to this code:

 ANIMATIONSHELPER.launchAnimation = function (animation) {
    // Launching the tick service if required
    if (ANIMATIONSHELPER.tickIntervalID == 0) {
        ANIMATIONSHELPER.tickIntervalID = setInterval(ANIMATIONSHELPER.tick, 17);
    }

    // Little closure to launch the tick method on the appropriate animation instance
    setInterval(function () { animation.tick(); }, 17);
};

At last, we have this kind of code that helps us building the keyframes:

 // Object to build a new generic keyframe (not working on the CSS3 2D Transform properties thus)
ANIMATIONSHELPER.keyframe = function (percentage, propertyToAnimate, value) {
    this.percentage = percentage;
    this.propertyToAnimate = propertyToAnimate;
    this.value = value;
};

//Objects to build specific rotation keyframes
ANIMATIONSHELPER.rotationkeyframe = function (percentage, value) {
    this.percentage = percentage;
    this.propertyToAnimate = "transform";
    this.transformType = [];
    this.transformType.push(new ANIMATIONSHELPER.transformType("rotate", value));
};

To highlight its usage, let’s recreate the previous simple CSS3 Animation skull sample with this library :

 // number of times you'd like the animations to be run
var iterationsNumber = 100;

var skullElement = document.getElementById("skull");
var keyframes = [];
keyframes.push(new ANIMATIONSHELPER.rotationkeyframe(25, 15));
keyframes.push(new ANIMATIONSHELPER.rotationkeyframe(50, -5));
keyframes.push(new ANIMATIONSHELPER.rotationkeyframe(55, 0));
keyframes.push(new ANIMATIONSHELPER.rotationkeyframe(75, -10));
keyframes.push(new ANIMATIONSHELPER.rotationkeyframe(100, 0));

var animation1 = new ANIMATIONSHELPER.animation(skullElement, "rotate-skull", 7000, 
                                                iterationsNumber, keyframes);

ANIMATIONSHELPER.launchAnimation(animation1, ANIMATIONSHELPER.easingFunctions.linear);

And here is the result that will now work in every browser supporting CSS3 2D Transform:

At last, the very first sample demonstrated at the beginning of this article uses Modernizr to check the support for CSS3 Animations. If it’s not the case, it loads the code that will mimic the keyframes defined in the file master.css, moz-master.css & ms-master.css :

 // Checking if CSS3 animations is supported
if (!Modernizr.cssanimations) {
    // if so, we can use our JS fallback library
    supportElement.innerHTML = "CSS3 Animations <strong>are not supported</strong>";
    LoadJSAnimationsFallback();
}
else {
    // if CSS3 animation is supported, we have nothing to do. 
    // The *master.css stylesheets will be automatically applied & used.
    supportElement.innerHTML = "CSS3 Animations <strong>are supported</strong>";
}

The LoadJSAnimationsFallback() function is defined into jsfallback-master.js which simply contains all the keyframes declarations and the 19 animations needed to mimic the behavior created by Anthony in pure CSS3. In this approach, the designer then needs to rewrite all rules using the library. Another approach could be to parse one of the CSS file using an XHR call and to create dynamically the JavaScript calls to the library. This needs more work as you almost need to reimplement the CSS3 animations specifications in JavaScript!

You now have an idea on the way to build a fallback mechanism to support more browsers while starting to use the latest CSS3 specifications.

You can download the files for the main sample here: https://david.blob.core.windows.net/html5/css3atat/CSS3ATATNonMinified.zip

It contains the unminified versions of the animationsHelper.js, transitionsHelper.js, jsfallback-master.js JavaScript files as well as the various CSS3 declinaison files for the main vendors prefixes.

Some implementation usages

I’ve been using CSS3 Transitions & Animations in a couple of Web & Windows 8 projects I’ve been working on. Here are for instance 2 articles using them in 2 different areas:

- Modernizing your HTML5 Canvas games Part 1: hardware scaling & CSS3 where I’m using transition coupled with CSS3 3D Transform to achieve a cool effect between each game’s levels

- Tutorial Series: using WinJS & WinRT to build a fun HTML5 Camera Application for Windows 8 (2/4) which is part of a 4 articles series to build a Windows 8 Store application. It uses CSS3 Animations to provide feedbacks to the user and symbolize a screenshot being done.

Conclusion

CSS3 Animations is a powerful technology to push HTML5 applications to a new level. It offers interesting scenarios. Designers could use it to create a new generation of UI screens with smooth & fluid animations without the need of developers. As it’s hardware-accelerated most of the time, developers should also pay attention to this specification. At last, both could collaborate. Designers could work on a series of predefined animations covering most scenarios. Developers could then create JavaScript libraries that will implement those animations. This library could offer in a transparent way 2 implementations: a dynamic generation of CSS3 on the fly or a fallback for older browsers.

Going further

 

David Rousset

Comments

  • Anonymous
    December 14, 2011
    Nice tutorial. Agreed it would be great to have a generalized fallback solution so folks could have that as a reasonable option for nonsupporting browsers. Good stuff!

  • Anonymous
    December 14, 2011
    Thanks for your comment Paul! Let's hope that this article will motivate someone in the community to work on a nice CSS3 animation fallback library. ;-)

  • Anonymous
    February 06, 2012
    Very cool.  Now I need to come up with a reason to use this!

  • Anonymous
    February 06, 2012
    The comment has been removed

  • Anonymous
    February 06, 2012
    Interesting... I got a link to this in an email from Microsoft.... and this is a Microsoft site... but IE does not support most CSS3.

  • Anonymous
    February 06, 2012
    The comment has been removed

  • Anonymous
    March 21, 2012
    I have been seeing this animation all over the web. Great work, thanks for the step by step demo too. All you need now is an animated A-Wing...

  • Anonymous
    April 07, 2012
    Css is the best for the 2D and 3D

  • Anonymous
    April 15, 2012
    I liked css3 features. It is very powerfully and light weighted. Not required to write 100 lines of code to do animations reduced testing efforts.

  • Anonymous
    July 21, 2013
    Excellent article. Just added a 5 star rating

  • Anonymous
    January 07, 2014
    really nice article !!!!!

  • Anonymous
    April 20, 2014
    Great article! Makes it very easy to understand!

  • Anonymous
    May 07, 2014
    Nice article, You could use AniJS for handling your CSS animations, AniJS is a  Declarative handling library for CSS animations. http://anijs.github.io/

  • Anonymous
    September 01, 2014
    Very valuable post! Everything is very clear after reading it! One of the best article, like this one: basicuse.net/.../animation_in_css3 There are many articles without explanations on Internet which annoys a lot! This one and mentioned above are the best, it's my personal opinion.

  • Anonymous
    September 01, 2014
    Thanks Tobey! :)