Introduction to CSS3 Transitions

A good-looking application must provide user with visual feedback. User must always know that an order (a click, a tap or whatever) is well received and understood by the application andanimations are a great tool to do so.

The new HTML 5 specification (to be honest, I should say “the new CSS 3 specification”) introduces a great tool to handle simple animations: the transitions.

According to “CSS Transitions Module Level 3” specification on W3C site, CSS3 Transitionsallows property changes in CSS values to occur smoothly over a specified duration.

The aim of this article will be to first describe the concept of transitions and then to see how CSS3 Transitions works and how we can handle browsers that don’t support the feature:HTML5 Powered with CSS3 / Styling

  1. CSS3 Transitions
  2. Putting it all together
  3. Transitions without CSS3 Transitions
  4. Conclusion
  5. Going further

In addition, I suggest you to read the “Introduction to CSS3 Animations” (by David Rousset) which is an excellent companion for this article.

To see how CSS3 Transitions can be used, I developed is a sample of a game which uses CSS3 Transitions to animate cells of a puzzle (and which will fallback to JavaScript if your browser doesn’t support CSS3 Transitions). You can play de game here.

 

CSS3 Transitions

Introduction

At the beginning, the W3C CSS workgroup resisted adding transitions to CSS arguing that transitions are not really style properties. But eventually designers and developers managed to convince them that transitions is about dynamic styles and can take place in a CSS file.

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.

Declarations

To declare a transition in a CSS file, you just have to write the following code:

  1. transition-property: all;
  2. transition-duration: 0.5s;
  3. transition-timing-function: ease;
  4. transition-delay: 0s;

This declaration defines that any update on any property will be done in 0.5s (and not immediately soSourire).

You can also define your translations on a per property basis:

  1. transition-property: opacity left top;
  2. transition-duration: 0.5s 0.8s 0.1s;
  3. transition-timing-function: ease linear ease;
  4. transition-delay: 0s 0s 1s;

And finally you can use the shorthand property “transition” to define all you need in a single line:

  1. transition: all 0.5s ease 0s;

In this shorthand version you can precise as many properties as you want separated by a comma:

  1. transition: opacity 0.5s ease 0s, left 0.8s linear 0s;

The transitions will be triggered when a property of the target object is updated. The update can be done with JavaScript or using CSS3 by assign new class to a tag.

For example, using IE10 if you have the following CSS3 declaration:

  1. -ms-transition-property: opacity left top;
  2. -ms-transition-duration: 0.5s 0.8s 0.5s;
  3. -ms-transition-timing-function: ease linear ease;

When you update the opacity of your tag, the current value will be animated to the new value over 0.5s with a ease timing function (which give a smooth animation).

Non-linear transitions

The “transition-timing-function” line defines that the transition will not be linear but will use a timing function to produce a non linear animation.

Basically, CSS3 transitions will use cubic bezier curve to smooth the transition 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 et 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 (using SVG of courseSourire) to show the impact of each timing function:

 

This simulator is written with pure JavaScript code to facilitate the understanding of the function:

  1. TRANSITIONSHELPER.computeCubicBezierCurveInterpolation = function (t, x1, y1, x2, y2) {
  2.     // Extract X (which is equal to time here)
  3.     var f0 = 1 – 3 * x2 + 3 * x1;
  4.     var f1 = 3 * x2 – 6 * x1;
  5.     var f2 = 3 * x1;
  6.     var refinedT = t;
  7.     for (var i = 0; i < 5; i++) {
  8.         var refinedT2 = refinedT * refinedT;
  9.         var refinedT3 = refinedT2 * refinedT;
  10.         var x = f0 * refinedT3 + f1 * refinedT2 + f2 * refinedT;
  11.         var slope = 1.0 / (3.0 * f0 * refinedT2 + 2.0 * f1 * refinedT + f2);
  12.         refinedT -= (x – t) * slope;
  13.         refinedT = Math.min(1, Math.max(0, refinedT));
  14.     }
  15.     // Resolve cubic bezier for the given x
  16.     return 3 * Math.pow(1 – refinedT, 2) * refinedT * y1 +
  17.             3 * (1 – refinedT) * Math.pow(refinedT, 2) * y2 +
  18.             Math.pow(refinedT, 3);
  19. };

This code is the implementation of the cubic bezier based on this definition and you can find the source of the simulator here.

Delay

The “transition-delay” line defines the delay between an update of a property and the start of the transition

Events

An event is raised at the end of a transition: “TransitionEnd”. According to your browser the correct name will be:

  • Chrome & Safari: webkitTransitionEnd
  • Firefox: mozTransitionEnd
  • Opera: oTransitionEnd
  • Internet Explorer: MSTransitionEnd

The event will give you the following information:

  • propertyName: Name of the animated property
  • elapsedTime: The amount of time the transition has been running, in seconds

Here is an usage sample for IE10:

  1. block.addEventListener(“MSTransitionEnd”, onTransitionEvent);

More about CSS3 transitions

I can mainly propose two reasons why CSS3 transitions are really useful:

  • Hardware acceleration: CSS3 Transitions are directly handled on the GPU (where available) and produce smoother results. And it is really important on mobile devices where computing power is really limited
  • Better separation between code and design: For me, the developer must not be aware of animations or anything related to design. In the same way the designer/artist must not be aware of JavaScript. That’s why CSS3 Transitions are really interesting as designers can describe all the transitions in the CSS without needing developers

Support and fallback

Since PP3, IE10 (which you can download with Windows “8” Developer Preview here) supports CSS3 Transitions:

image

This report was produced by http://caniuse.com/#search=CSS3 transitions.

Of course, as the specification is not finished (working draft), you must use vendor’s prefixes such as –ms-, –moz-, –webkit-, –o-.

We can obviously see that we need to provide a transparent solution in order to address all kind of browsers. The best way will be to develop an API that can detect the support of CSS3 transitions. If the browser doesn’t support the feature, we will fallback to some JavaScript code.

It is important to support a fallback method if you rely on transitions for websites functionalities. If you don’t want to do that, you should consider using transitions only for design enhancements. In this case, the site will still work but only supported browsers will deliver the full experience. We speak here of “progressive enhancements” as the more powerfull the browser is, the more features he gets.

Transitions without CSS3 Transitions

So to be able to support a fallback to CSS3 Transitions, we will develop a small toolkit to provide transitions by code.

First of all, we will create a container object for our namespace:

  1. var TRANSITIONSHELPER = TRANSITIONSHELPER || {};
  2. TRANSITIONSHELPER.tickIntervalID = 0;
  3. TRANSITIONSHELPER.easingFunctions = {
  4.     linear:0,
  5.     ease:1,
  6.     easein:2,
  7.     easeout:3,
  8.     easeinout:4,
  9.     custom:5
  10. };
  11. TRANSITIONSHELPER.currentTransitions = [];

To support the same level of easing functions, we must declare an “enum” with all required fields.

The toolkit is based on a function which is called every 17ms (to achieve animations at 60 fps). The function will enumerate through a collection of active transitions. For each transition the code will evaluate the next value given the current value and the target value.

We will need some handy functions to extract value of properties and units used:

  1. TRANSITIONSHELPER.extractValue = function (string) {
  2.     try {
  3.         var result = parseFloat(string);
  4.         if (isNaN(result)) {
  5.             return 0;
  6.         }
  7.         return result;
  8.     } catch (e) {
  9.         return 0;
  10.     }
  11. };
  12. TRANSITIONSHELPER.extractUnit = function (string) {
  13.     // if value is empty we assume that it is px
  14.     if (string == “”) {
  15.         return “px”;
  16.     }
  17.     var value = TRANSITIONSHELPER.extractValue(string);
  18.     var unit = string.replace(value, “”);
  19.     return unit;
  20. };

The main function will process active transitions and will call the cubic bezier function to evaluate current values:

  1. TRANSITIONSHELPER.tick = function () {
  2.     // Processing transitions
  3.     for (var index = 0; index < TRANSITIONSHELPER.currentTransitions.length; index++) {
  4.         var transition = TRANSITIONSHELPER.currentTransitions[index];
  5.         // compute new value
  6.         var currentDate = (new Date).getTime();
  7.         var diff = currentDate – transition.startDate;
  8.         var step = diff / transition.duration;
  9.         var offset = 1;
  10.         // Timing function
  11.         switch (transition.ease) {
  12.             case TRANSITIONSHELPER.easingFunctions.linear:
  13.                 offset = TRANSITIONSHELPER.computeCubicBezierCurveInterpolation(step, 0, 0, 1.0, 1.0);
  14.                 break;
  15.             case TRANSITIONSHELPER.easingFunctions.ease:
  16.                 offset = TRANSITIONSHELPER.computeCubicBezierCurveInterpolation(step, 0.25, 0.1, 0.25, 1.0);
  17.                 break;
  18.             case TRANSITIONSHELPER.easingFunctions.easein:
  19.                 offset = TRANSITIONSHELPER.computeCubicBezierCurveInterpolation(step, 0.42, 0, 1.0, 1.0);
  20.                 break;
  21.             case TRANSITIONSHELPER.easingFunctions.easeout:
  22.                 offset = TRANSITIONSHELPER.computeCubicBezierCurveInterpolation(step, 0, 0, 0.58, 1.0);
  23.                 break;
  24.             case TRANSITIONSHELPER.easingFunctions.easeinout:
  25.                 offset = TRANSITIONSHELPER.computeCubicBezierCurveInterpolation(step, 0.42, 0, 0.58, 1.0);
  26.                 break;
  27.             case TRANSITIONSHELPER.easingFunctions.custom:
  28.                 offset = TRANSITIONSHELPER.computeCubicBezierCurveInterpolation(step, transition.customEaseP1X, transition.customEaseP1Y, transition.customEaseP2X, transition.customEaseP2Y);
  29.                 break;
  30.         }
  31.         offset *= (transition.finalValue – transition.originalValue);
  32.         var unit = TRANSITIONSHELPER.extractUnit(transition.target.style[transition.property]);
  33.         var currentValue = transition.originalValue + offset;
  34.         transition.currentDate = currentDate;
  35.         // Dead transition?
  36.         if (currentDate >= transition.startDate + transition.duration) {
  37.             currentValue = transition.finalValue; // Clamping
  38.             TRANSITIONSHELPER.currentTransitions.splice(index, 1); // Removing transition
  39.             index–;
  40.             // Completion event
  41.             if (transition.onCompletion) {
  42.                 transition.onCompletion({propertyName:transition.property, elapsedTime:transition.duration});
  43.             }
  44.         }
  45.         // Affect it
  46.         transition.target.style[transition.property] = currentValue + unit;
  47.     }
  48. };

The current version of the toolkit only supports numeric values but if you want to animate complex values (such as color) you just have to decompose them to simple values.

Registering a transition in the system will be done using the following code:

  1. TRANSITIONSHELPER.transition = function (target, property, newValue, duration, ease, customEaseP1X, customEaseP1Y, customEaseP2X, customEaseP2Y, onCompletion) {
  2.     // Create a new transition
  3.     var transition = {
  4.         target: target,
  5.         property: property,
  6.         finalValue: newValue,
  7.         originalValue: TRANSITIONSHELPER.extractValue(target.style[property]),
  8.         duration: duration,
  9.         startDate: (new Date).getTime(),
  10.         currentDate: (new Date).getTime(),
  11.         ease:ease,
  12.         customEaseP1X:customEaseP1X,
  13.         customEaseP2X:customEaseP2X,
  14.         customEaseP1Y: customEaseP1Y,
  15.         customEaseP2Y: customEaseP2Y,
  16.         onCompletion: onCompletion
  17.     };
  18.     // Launching the tick service if required
  19.     if (TRANSITIONSHELPER.tickIntervalID == 0) {
  20.         TRANSITIONSHELPER.tickIntervalID = setInterval(TRANSITIONSHELPER.tick, 17);
  21.     }
  22.     // Remove previous transitions on same property and target
  23.     for (var index = 0; index < TRANSITIONSHELPER.currentTransitions.length; index++) {
  24.         var temp = TRANSITIONSHELPER.currentTransitions[index];
  25.         if (temp.target === transition.target && temp.property === transition.property) {
  26.             TRANSITIONSHELPER.currentTransitions.splice(index, 1);
  27.             index–;
  28.         }
  29.     }
  30.     // Register
  31.     if (transition.originalValue != transition.finalValue) {
  32.         TRANSITIONSHELPER.currentTransitions.push(transition);
  33.     }
  34. };

The “tick” function is launched when the first transition is activated.

Finally you just have to use modernizr to define if CSS3 Transitions is supported by the current browser. If not, you can fallback to our toolkit.

The code for the TransitionsHelper can be downloaded here: http://www.catuhe.com/msdn/transitions/transitionshelper.js

For example, in my puzzle game, the following code is used to animate the cells:

  1. if (!PUZZLE.isTransitionsSupported) {
  2.     TRANSITIONSHELPER.transition(block.div, “top”, block.x * totalSize + offset, 500, TRANSITIONSHELPER.easingFunctions.ease);
  3.     TRANSITIONSHELPER.transition(block.div, “left”, block.y * totalSize + offset, 500, TRANSITIONSHELPER.easingFunctions.ease);
  4. }
  5. else {
  6.     block.div.style.top = (block.x * totalSize + offset) + “px”;
  7.     block.div.style.left = (block.y * totalSize + offset) + “px”;
  8. }

We can note that I could use another way to animate my cells when CSS3 transitions are supported: I could have defined a collection of CSS3 classes with predefined left and top values (one for each cell) to affect them to right cells.

Some frameworks and toolkits already exist to support software transitions:

By the way, you can also use the old good animate() method of jQuery.

Conclusion

As we saw, CSS3 Transitions is a really easy way to add animations to your project. You can produce a more reactive application just by using some transitions when you want to change values.

By the way, there are two solutions if you want to implement a JavaScript fallback:

  • You can do all in the JavaScript side and if you detect the support of CSS3 transitions, you will inject CSS3 declarations in the page.
  • Or you can use standard way (using true CSS3 declarations in the CSS files) and just detect the need of fallback in JavaScript. For me, it is the better option as the fallback must be an option and not the main subject. In a near future, all browsers will support CSS3 Transitions and in this case you will just have to remove your fallback code. Furthermore, it is a better way to let all the CSS under the control of the creative team and not in the code part.