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:
- CSS3
Animations
- Browsers
Support
- CSS3
Animations JavaScript fallback library
- 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: http://david.blob.core.windows.net/html5/css3atat/index.htm
Note: this
sample has been tested successfully with native animations under IE10 PP3/PP4,
Chrome 15, Firefox 8 & iPad 2 and with JS fallback under IE9 desktop &
mobile (Windows Phone). For an unknown reason, it behaves in weird way under
Opera 11.50 but works fine with the 11.60. Moreover, our lovely blogging
platform is most of the time forcing the IE9 rendering engine via a meta tag.
To force it back to the IE10 standards mode, press the F12 key and change the
value of "Document Mode" back to IE10. Otherwise, view the demo in a separate
window.
This sample is based on the
awesome work done by
Anthony
Calzadilla.
You can check other incredible demos on his website here:
http://www.anthonycalzadilla.com . I’m a
huge fan of the
I twitty the
fool
sample using SVG & CSS3 Animation for instance.
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: http://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: http://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. First of all, as the CSS3 Animations specification is
still in a draft stage, you’ll need to use the appropriate vendor prefix. Let’s
use IE10 as a sample with the –ms prefix then. Let’s now see how the head of
our AT-AT is moving.
Here’s the animation declaration:
@-ms-keyframes rotate-skull {
0% {
-ms-transform: rotate(0deg)
}
25% {
-ms-transform: rotate(15deg)
}
50% {
-ms-transform: rotate(-5deg)
}
55% {
-ms-transform: rotate(0deg)
}
75% {
-ms-transform: rotate(-10deg)
}
100% {
-ms-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
{
-ms-animation-name: rotate-skull;
-ms-animation-duration: 7s;
-ms-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.
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 {
-ms-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:
@-ms-keyframes demo {
from {
-ms-animation-timing-function: ease;
-ms-transform: translateX(0px);
}
50% {
-ms-animation-timing-function: ease-in;
-ms-transform: translateX(300px);
}
to {
-ms-animation-timing-function: ease-inout;
-ms-transform: translateX(900px);
}
}
#testCanvas
{
-ms-animation-delay: 0s;
-ms-animation-duration: 6s;
-ms-animation-iteration-count: infinite;
-ms-animation-name: demo;
}
As well as all the vendor
prefixes variations to make it works also in Google Chrome & Mozilla
Firefox. And here’s the living output:
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:
- Chrome:
webkitAnimationEnd
- Firefox: mozAnimationEnd
- Internet Explorer:
MSAnimationEnd
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:
elementToAnimate.addEventListener("MSAnimationEnd", 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 a iPad 2. This is
because its CPU is much more limited and the iPad is 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
Since the Platform Preview 3 of
IE10 available in the Windows
Developer Preview, we’re supporting CSS3 Animations. And as you can
see on the following report produced by caniuse.com, the
CSS3 animations are now supported on a wide range of browsers:
But as the specification is not
finished yet (working draft), you must use vendor’s prefixes such as
–ms-, –moz-, –webkit-, –o- 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:
ANIMATIONSHELPER.animation = function (target, name, duration, iterationcount, keyframes) {
this.name = name;
this.duration = duration;
this.iterationcount = iterationcount;
this.target = target;
var elapsedtime = 0;
var keyframeduration = 0;
var elapsedtime = 0;
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;
this.nextTransition = function (keyframe, ease, customEaseP1X, customEaseP1Y, customEaseP2X, customEaseP2Y) {
var properties = [];
var finalValues = [];
var transition;
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 });
}
}
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;
}
else {
return TRANSITIONSHELPER.transition(this.target, keyframe.propertyToAnimate, keyframe.value, keyframe.duration, TRANSITIONSHELPER.easingFunctions.linear);
}
};
this.tick = function () {
if (this.iterationcount > 0) {
if (!this.currentTransition.isPlaying) {
this.currentTransition = this.nextTransition(this.keyframes[this.currentKeyFrameIndex], ANIMATIONSHELPER.easingFunctions.linear);
if (this.currentTransition.type === "transform") {
ANIMATIONSHELPER.currentTransitions.push(this.currentTransition);
}
this.currentKeyFrameIndex++;
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) {
if (ANIMATIONSHELPER.tickIntervalID == 0) {
ANIMATIONSHELPER.tickIntervalID = setInterval(ANIMATIONSHELPER.tick, 17);
}
setInterval(function () { animation.tick(); }, 17);
};
At last, we have this kind of
code that helps us building the keyframes:
ANIMATIONSHELPER.keyframe = function (percentage, propertyToAnimate, value) {
this.percentage = percentage;
this.propertyToAnimate = propertyToAnimate;
this.value = value;
};
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 :
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 :
if (!Modernizr.cssanimations) {
supportElement.innerHTML = "CSS3 Animations <strong>are not supported</strong>";
LoadJSAnimationsFallback();
}
else {
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: http://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.
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