> Manuales > Manual de CSS 3

Aplicaciones atractivas con transiciones CSS3.

Una aplicación atractiva tiene que provocar un impacto visual agradable en el usuario. Los usuarios siempre han de saber que cualquier orden suya (mediante clic del ratón, una pulsación en pantalla o cualquier otra forma) se recibe e interpreta de forma correcta en la aplicación, y las animaciones ofrecen una manera muy interesante de conseguir esto.

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í.

Introducción

Al principio, el grupo de trabajo de CSS dentro del W3C se resistía a integrar las transiciones dentro de CSS argumentando que en realidad no son propiedades de los estilos, pero finalmente los diseñadores y desarrolladores consiguieron convencerles de que las transiciones son en realidad estilos dinámicos y que pueden tener cabida dentro de un archivo CSS.

Según se puede leer en el sitio web del W3C, CSS3 Transitions permite crear variaciones progresivas sobre los siguientes tipos de propiedades:

  1. Color: interpolación entre los componentes rojo, verde, azul y alpha (tratando a cada uno como un número, como se verá más adelante).
  2. Longitud: interpolando entre valores expresados como números reales.
  3. Porcentaje: interpolando entre valores expresados como números reales.
  4. Entero: interpolado en pasos discretos (números completos). La interpolación tiene lugar en el espacio de los números reales y se convierte a entero utilizando la función floor().
  5. Número: se interpola entre valores expresados como números reales (coma flotante).
  6. Lista de transformación: puedes consultar la especificación CSS Transforms en: http://www.w3.org/TR/css3-2d-transforms/.
  7. Rectángulo: se interpolan los valores x e y, y los componentes de anchura y altura (todos estos valores se tratan como números).
  8. Visibilidad: se interpola en forma de pasos discretos. La interpolación se produce en el espacio de números entre el cero, el 1, donde 1 significa "visible" y el resto de valores son "hidden" (ocultos).
  9. Sombra: se interpolan los valores de color, coordenadas x e y, y el componente de difuminado (blur), tratando todos los valores como de color o número según corresponda. En aquellos casos donde existen listas de sombras, la lista más corta se rellena al final con sombras de color transparente y todas las longitudes (x,y y difuminado), con valor cero.
  10. Gradiente: se interpolan los valores de posición y color en cada parada. Tienen que ser del mismo tipo (radial o lineal ) y con el mismo número de paradas para poder realizar la animación.
  11. Servidor de dibujo (SVG): la interpolación solo está soportada para las transiciones de gradiente a gradiente y de color a color. Funcionan como se ha descrito antes para cada uno de esos valores.
  12. Lista separada mediante espacios de los elementos anteriores: si la lista tiene el mismo número de elementos, cada elemento dentro de ella se interpola siguiendo las reglas anteriores. Si no, no se produce la interpolación.
  13. Una propiedad abreviada: si todas las partes de una abreviatura se pueden animar, la interpolación se efectúa como si se hubiera especificado cada una de las propiedades de manera individual.
Y esta es la lista de propiedades que se pueden modificar mediante transiciones:
  1. background-color (color)
  2. background-image (solo gradientes)
  3. background-position (porcentaje y longitud)
  4. border-bottom-color (color)
  5. border-bottom-width (longitud)
  6. border-color (color)
  7. border-left-color (color)
  8. border-left-width (longitud)
  9. border-right-color (color)
  10. border-right-width (longitud)
  11. border-spacing (longitud)
  12. border-top-color (color)
  13. border-top-width (longitud)
  14. border-width (longitud)
  15. bottom (longitud y porcentaje)
  16. color (color)
  17. crop (rectángulo)
  18. font-size (longitud y porcentaje)
  19. font-weight (número)
  20. grid-* (diversos valores)
  21. height (longitud y porcentaje)
  22. left (longitud y porcentaje)
  23. letter-spacing (longitud)
  24. line-height (número, longitud y porcentaje)
  25. margin-bottom (longitud)
  26. margin-left (longitud)
  27. margin-right (longitud)
  28. margin-top (longitud)
  29. max-height (longitud y porcentaje)
  30. max-width (longitud y porcentaje)
  31. min-height (longitud y porcentaje)
  32. min-width (longitud y porcentaje)
  33. opacity (número)
  34. outline-color (color)
  35. outline-offset (entero)
  36. outline-width (longitud)
  37. padding-bottom (longitud)
  38. padding-left (longitud)
  39. padding-right (longitud)
  40. padding-top (longitud)
  41. right (longitud y porcentaje)
  42. text-indent (longitud y porcentaje)
  43. text-shadow (sombra)
  44. top (longitud y porcentaje)
  45. vertical-align (palabras clave, longitud y porcentaje)
  46. visibility (visibilidad)
  47. width (longitud y porcentaje)
  48. word-spacing (longitud y porcentaje)
  49. z-index (entero)
  50. zoom (número)

SVG

Las propiedades de los objetos SVG son susceptibles de animación si se definen con la cláusula animatable:true en la especificación SVG: http://www.w3.org/TR/SVG/struct.html.

Declaraciones

Para declarar una transición en un archivo CSS nos basta con escribir el siguiente código:

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.

Transiciones no lineales

La línea "transition-timing-function" indica que la transición no va a ser lineal, sino que utilizará una función de tiempo que genera una animación no lineal. Básicamente, las transiciones CSS3 utilizan la función de curva de Bezier cúbica para suavizar las transiciones mediante el cálculo de distintos valores de velocidad durante el intervalo de ejecución.

Hay otras funciones de transición también soportadas:

  1. Linear: transición a velocidad constante
  2. Cubic-bezier: la velocidad se calcula de acuerdo con la función de curva Bezier cúbica definida por dos puntos de control, P0 y P1 (así que tenemos que definir 4 valores en este caso: P0x-P0y y P1x- P1y)
  3. Ease: La velocidad se calcula según la función de curva Bezier cúbica con los parámetros (0.25, 0.1, 0.25, 1)
  4. Ease-in: La velocidad se calcula con la función de curva Bezier con los parámetros (0.42, 0, 1, 1)
  5. Ease-inout: La velocidad se calcula con la función de curva Bezier con los parámetros (0.42, 0, 0.58, 1)
  6. Ease-out: La velocidad se calcula con la función de curva Bezier con los parámetros (0, 0, 0.58, 1)
En http://www.catuhe.com/msdn/transitions/easingfunctions.htm tenemos una herramienta de simulación (utilizando SVG por supuesto) para mostrar el impacto de cada una de estas funciones de ajuste.

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í.

Retardo

La línea "transition-delay" determina el retardo que se produce entre el momento en que se modifica el valor de la propiedad y el comienzo de la transición.

Eventos

Al final de cada transición se dispara un evento llamado "TransitionEnd". Dependiendo de qué navegador utilicemos, el nombre cambia: El evento nos pasa la siguiente información: Este sería un ejemplo de uso en IE10:

block.addEventListener("MSTransitionEnd", onTransitionEvent);

Más sobre las transiciones CSS3

Puedo sugerir dos excelentes razones por las cuales las transiciones CSS3 van a ser muy útiles:
  1. Aceleración por hardware: las transiciones basadas en CSS3 las maneja directamente la GPU (si existe en el equipo) dando lugar a resultados mucho más suaves. Y esto es verdaderamente importante en los dispositivos móviles, cuya capacidad de computación, en general, es limitada.
  2. Mayor independencia entre el código y el diseño: desde mi punto de vista, el desarrollador no debería tener que ocuparse de las animaciones ni de nada relacionado con el diseño. Por esa misma razón, el diseñador/artista no tendría que ocuparse del JavaScript. Por eso me parece que CSS3 Transitions es una novedad realmente interesante para los diseñadores, que pueden describir todas las transiciones utilizando CSS sin involucrar a los programadores.

Soporte y fallback

Desde la versión PP3, IE10 (que ya podemos descargar con la versión Preliminar de Desarrollo de Windows 8 desde aquí) soporta las transiciones CSS3:


Informe obtenido desde http://caniuse.com/#search=CSS3 transitions.

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.

Transiciones sin CSS3 Transitions

Así pues, para incluir un fallback para los navegadores no compatibles con CSS3 Transitions, vamos a desarrollar un pequeño kit de herramientas que harán lo mismo por programación.

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.

Conclusión

Como has podido comprobar, CSS3 Transitions es sin duda una forma muy sencilla de incorporar animaciones a nuestros proyectos. Podemos crear una aplicación más reactiva simplemente añadiendo algunas transiciones cuando vayamos a modificar ciertos valores.

Pero además tenemos dos alternativas para implementar un fallback con JavaScript:

  1. Podemos hacerlo todo con JavaScript, y si detectamos que el navegador soporta las transiciones CSS3, inyectar las declaraciones CSS3 en la página.
  2. O bien podemos utilizar la manera más ortodoxa (utilizando declaraciones CSS3 auténticas en los archivos CSS) y detectar si necesitamos o no aplicar un fallback desde JavaScript. Para mí esta es la mejor opción, puesto que el fallback tiene que considerarse una opción alternativa, no la principal. De cara al futuro próximo, todos los navegadores van a soportar CSS3 Transitions y en este caso solo tendríamos que eliminar el código de fallback. Pero además es que es la mejor (o si no, la única) manera de dejar todo el bloque CSS bajo el control del equipo de diseñadores, retirándolo en lo posible de la parte de programación.

Para saber más

David Rousset

Desarrollador Evangelista de Microsoft, especialista en HTML5 y desarrollo Web.

Manual