La nueva especificación HTML5 (aunque, en honor a la verdad, tendría que decir "la nueva especificación CSS3") incorpora una herramienta muy potente para gestionar animaciones sencillas: las transiciones.
Según se indica en la especificación "CSS Transitions Module Level 3" que podemos leer en el sitio web del W3C, las transiciones CSS3 permiten realizar cambios en los valores CSS de manera progresiva durante un tiempo determinado.
El objetivo de este artículo consiste, en primer lugar, en describir el concepto de transición y después ver cómo funciona CSS3 Transitions y cómo podemos manejar el resultado en los navegadores que no soportan esta funcionalidad.
Además, sugiero que os leáis el artículo "Introducción a las animaciones con CSS3" (escrito por mí mismo) que os puede servir de complemento ideal para este otro.
Para saber cómo se utilizan las transiciones con CSS3 he desarrollado un ejemplo de un juego que utiliza CSS3 Transitions para animar las fichas de un puzzle (y que incluye un fallback hecho con JavaScript si el navegador no soporta CSS3 Transitions ):
Para disponer de la versión ejecutable de este juego, visita mi blog aquí. El código del juego lo puedes encontrar aquí.

Según se puede leer en el sitio web del W3C, CSS3 Transitions permite crear variaciones progresivas sobre los siguientes tipos de propiedades:
transition-property: all;
transition-duration: 0.5s;
transition-timing-function: ease;
transition-delay: 0s;
Esta declaración indica que cualquier modificación del valor de cualquier propiedad debe hacerse en un intervalo de 0,5 segundos (y no de forma inmediata).
Y podemos también definir las transiciones a nivel individual para cada propiedad:
transition-property: opacity left top;
transition-duration: 0.5s 0.8s 0.1s;
transition-timing-function: ease linear ease;
transition-delay: 0s 0s 1s;
Finalmente, podemos utilizar la propiedad abreviada "transition" para definir todo lo necesario en una sola línea:
transition: all 0.5s ease 0s;
En esta versión abreviada se pueden incorporar todas las propiedades que queramos, separándolas con comas:
transition: opacity 0.5s ease 0s, left 0.8s linear 0s;
Las transiciones se disparan cuando se modifica una propiedad del objeto indicado. La modificación se puede hacer con JavaScript o utilizando CSS3 mediante la asignación de una nueva clase a una etiqueta.
Por ejemplo, si usamos IE10, tenemos la siguiente declaración CSS3:
-ms-transition-property: opacity left top;
-ms-transition-duration: 0.5s 0.8s 0.5s;
-ms-transition-timing-function: ease linear ease;
Cuando actualicemos la opacidad en nuestra etiqueta, se inicia una animación que va alterando progresivamente el valor desde el actual al nuevo, durante 0,5 segundos, con una función de ajuste no lineal a lo largo de ese intervalo (easing), que nos proporciona el efecto de una animación suave.
Hay otras funciones de transición también soportadas:
Este simulador está escrito totalmente en JavaScript para facilitar la comprensión de la función:
TRANSITIONSHELPER.computeCubicBezierCurveInterpolation = function (t, x1, y1, x2, y2) {
// Extrae X (que aquí equivale al tiempo)
var f0 = 1 - 3 * x2 + 3 * x1;
var f1 = 3 * x2 - 6 * x1;
var f2 = 3 * x1;
var refinedT = t;
for (var i = 0; i < 5; i++) {
var refinedT2 = refinedT * refinedT;
var refinedT3 = refinedT2 * refinedT;
var x = f0 * refinedT3 + f1 * refinedT2 + f2 * refinedT;
var slope = 1.0 / (3.0 * f0 * refinedT2 + 2.0 * f1 * refinedT + f2);
refinedT -= (x - t) * slope;
refinedT = Math.min(1, Math.max(0, refinedT));
}
// Resuelve la curba Bezier cúbica para el valor x dado
return 3 * Math.pow(1 - refinedT, 2) * refinedT * y1 +
3 * (1 - refinedT) * Math.pow(refinedT, 2) * y2 +
Math.pow(refinedT, 3);
};
Este código es la implementación de la curva Bezier cúbica basada en esta definición y puedes encontrar el código fuente del simulador aquí.
block.addEventListener("MSTransitionEnd", onTransitionEvent);
Sin duda, puesto que la especificación aún no está terminada (está en la fase working draft), nos va a tocar utilizar los prefijos de fabricante: -ms-, -moz-, -webkit-, -o-.
Podemos además ver que, obviamente, será necesaria una solución transparente para resolver la situación que se va a presentar en el resto de navegadores. Si el navegador no soporta la funcionalidad, tendremos que preparar un fallback programando con JavaScript.
Conviene tener preparado un método de fallback si las funcionalidades de tus sitios web dependen de las transiciones. Si no quieres hacerlo, deberías pensar en utilizar las transiciones solamente como mejoras de diseño. En este caso el sitio web seguirá funcionando, pero la experiencia completa solo se podrá ver en los navegadores que la soporten. Aquí solemos hablar de "mejoras progresivas" ya que cuanto más potente es el navegador, más funcionalidades nos permite ofrecer.
Lo primero, vamos a crear un objeto contenedor para nuestro espacio de nombres:
var TRANSITIONSHELPER = TRANSITIONSHELPER || {};
TRANSITIONSHELPER.tickIntervalID = 0;
TRANSITIONSHELPER.easingFunctions = {
linear:0,
ease:1,
easein:2,
easeout:3,
easeinout:4,
custom:5
};
TRANSITIONSHELPER.currentTransitions = [];
Para dar soporte al mismo nivel de funciones de velocidad de transición, tenemos que declarar un "enum" con todos los campos necesarios.
El kit de herramientas se basa en una función a la que se llama cada 17ms (para conseguir animaciones a 60fps). La función irá pasando a través de una colección de transiciones activas. Para cada transición, el código evalúa el siguiente valor calculado a partir del valor actual y el indicado como valor final.
Necesitamos una serie de funciones muy útiles para extraer el valor de las propiedades y las unidades de medida utilizadas:
TRANSITIONSHELPER.extractValue = function (string) {
try {
var result = parseFloat(string);
if (isNaN(result)) {
return 0;
}
return result;
} catch (e) {
return 0;
}
};
TRANSITIONSHELPER.extractUnit = function (string) {
// si el valor está vacío, suponemos que se expresa en px
if (string == "") {
return "px";
}
var value = TRANSITIONSHELPER.extractValue(string);
var unit = string.replace(value, "");
return unit;
};
La función principal procesará las transiciones activas y llamará a la función cúbica Bezier para calcular los valores actuales:
TRANSITIONSHELPER.tick = function () {
// Procesando transiciones
for (var index = 0; index < TRANSITIONSHELPER.currentTransitions.length; index++) {
var transition = TRANSITIONSHELPER.currentTransitions[index];
// calcula el nuevo valor
var currentDate = (new Date).getTime();
var diff = currentDate - transition.startDate;
var step = diff / transition.duration;
var offset = 1;
// Función de timing
switch (transition.ease) {
case TRANSITIONSHELPER.easingFunctions.linear:
offset = TRANSITIONSHELPER.computeCubicBezierCurveInterpolation(step, 0, 0, 1.0, 1.0);
break;
case TRANSITIONSHELPER.easingFunctions.ease:
offset = TRANSITIONSHELPER.computeCubicBezierCurveInterpolation(step, 0.25, 0.1, 0.25, 1.0);
break;
case TRANSITIONSHELPER.easingFunctions.easein:
offset = TRANSITIONSHELPER.computeCubicBezierCurveInterpolation(step, 0.42, 0, 1.0, 1.0);
break;
case TRANSITIONSHELPER.easingFunctions.easeout:
offset = TRANSITIONSHELPER.computeCubicBezierCurveInterpolation(step, 0, 0, 0.58, 1.0);
break;
case TRANSITIONSHELPER.easingFunctions.easeinout:
offset = TRANSITIONSHELPER.computeCubicBezierCurveInterpolation(step, 0.42, 0, 0.58, 1.0);
break;
case TRANSITIONSHELPER.easingFunctions.custom:
offset = TRANSITIONSHELPER.computeCubicBezierCurveInterpolation(step, transition.customEaseP1X, transition.customEaseP1Y, transition.customEaseP2X, transition.customEaseP2Y);
break;
}
offset *= (transition.finalValue - transition.originalValue);
var unit = TRANSITIONSHELPER.extractUnit(transition.target.style[transition.property]);
var currentValue = transition.originalValue + offset;
transition.currentDate = currentDate;
// ¿Transición muerta?
if (currentDate >= transition.startDate + transition.duration) {
currentValue = transition.finalValue; // Clamping
TRANSITIONSHELPER.currentTransitions.splice(index, 1); // Removing transition
index--;
// Evento final
if (transition.onCompletion) {
transition.onCompletion({propertyName:transition.property, elapsedTime:transition.duration});
}
}
// Valor final
transition.target.style[transition.property] = currentValue + unit;
}
};
La versión actual del kit de herramientas solo soporta valores numéricos, pero si quieres animar valores complejos (como el color), simplemente tienes que descomponerlos en valores individuales.
El registro de una transición en el sistema se haría utilizando este código:
TRANSITIONSHELPER.transition = function (target, property, newValue, duration, ease, customEaseP1X, customEaseP1Y, customEaseP2X, customEaseP2Y, onCompletion) {
// Creamos una nueva transición
var transition = {
target: target,
property: property,
finalValue: newValue,
originalValue: TRANSITIONSHELPER.extractValue(target.style[property]),
duration: duration,
startDate: (new Date).getTime(),
currentDate: (new Date).getTime(),
ease:ease,
customEaseP1X:customEaseP1X,
customEaseP2X:customEaseP2X,
customEaseP1Y: customEaseP1Y,
customEaseP2Y: customEaseP2Y,
onCompletion: onCompletion
};
// Arranca el sevicio de tick si es preciso
if (TRANSITIONSHELPER.tickIntervalID == 0) {
TRANSITIONSHELPER.tickIntervalID = setInterval(TRANSITIONSHELPER.tick, 17);
}
// Elimina las transiciones anteriores para la misma propiedad y objeto
for (var index = 0; index < TRANSITIONSHELPER.currentTransitions.length; index++) {
var temp = TRANSITIONSHELPER.currentTransitions[index];
if (temp.target === transition.target && temp.property === transition.property) {
TRANSITIONSHELPER.currentTransitions.splice(index, 1);
index--;
}
}
// Registramos
if (transition.originalValue != transition.finalValue) {
TRANSITIONSHELPER.currentTransitions.push(transition);
}
};
La función "tick" arranca la primera vez que se activa la transición.
Finalmente, tendremos que utilizar modernizr para saber si el navegador soporta CSS3 Transitions. Si no, podemos dejar nuestro kit como fallback.
El código para el TransitionsHelper se puede descargar desde aquí: http://www.catuhe.com/msdn/transitions/transitionshelper.js
Por ejemplo, en mi juego de puzzle, utilizo este código para animar las piezas:
if (!PUZZLE.isTransitionsSupported) {
TRANSITIONSHELPER.transition(block.div, "top", block.x * totalSize + offset, 500, TRANSITIONSHELPER.easingFunctions.ease);
TRANSITIONSHELPER.transition(block.div, "left", block.y * totalSize + offset, 500, TRANSITIONSHELPER.easingFunctions.ease);
}
else {
block.div.style.top = (block.x * totalSize + offset) + "px";
block.div.style.left = (block.y * totalSize + offset) + "px";
}
Se puede ver que podría haber empleado otra solución para animar las cuadrículas en el caso de que CSS3 Transitions estuviera soportado: podría haber definido una colección de clases CSS3 con valores predefinidos left y top (uno por cada celda) para aplicarlos a las piezas correspondientes.
Ya existen algunos entornos y kits de desarrollo que soportan las transiciones por software:
Y por supuesto, siempre podemos utilizar el conocido y potente método animate() de jQuery.Pero además tenemos dos alternativas para implementar un fallback con JavaScript: