CSS3: Animaciones

Introducción

Las animaciones de CSS3 nos permiten modificar ciertas propiedades CSS durante un determinado tiempo para conseguir un efecto animado de un elemento HTML. Son similares a las transiciones, aunque en éstas el cambio se produce cuando el valor de la propiedad es modificado, mientras que la animación se inicia cuando las propiedades son aplicadas. Podemos controlar el inicio, fin y estados intermedios haciendo uso de marcos clave (keyframes).

La especificación oficial CSS Animations se encuentra actualmente en fase WD (Working Draft). Aunque no es está en fase estándar, los navegadores importantes la soportan ampliamente y es razonable esperar que esta especificación no sufra cambios importantes. En este tema expondré lo básico para entender y usar las animaciones de CSS3. A continuación empezaremos con un simple ejemplo:

Ejemplo:

ANIMADO

Código completo de este ejemplo

Este ejemplo mueve la palabra ANIMADO a lo largo de los cuatro lados del contenedor con borde rojo. Esa palabra es el texto interior de un elemento <div> con id="animado". Tiene posicionamiento absoluto para actuar sobre sus propiedades left y top. Para conseguir la animación necesitamos declarar los keyframes (marcos clave). Esto lo hacemos en una regla arroba que por lo tanto debe ir en estilo no en línea (elemento <style> o archivo CSS externo). En el ejemplo tenemos esto:

@keyframes ejemplo {
    from {
        left: 0;
        top: 0;
    }
    25% {
        left: 0;
        top: 100px;
    }
    50% {
        left: 200px;
        top: 100px;
    }
    75% {
        left: 200px;
        top: 0;
    }
    to {
        left: 0;
        top: 0;
    }
}

El nombre de la animación de este conjunto de marcos es ejemplo, nombre que se dará a la propiedad animation-name ejecutando esta secuencia. Establece un inicio (from) un final (to) y 3 estados intermedios. Esto hace que el elemento se desplace por los cuatro lados del contenedor externo. Las palabras claves from y to equivalen a 0% y 100% respectivamente. El porcentaje indica el momento en el cual se ejecuta cada marco, calculado a partir de la duración total de la animación, establecida en la propiedad animation-duration y que tiene un valor predeterminado de 1 segundo. Para este ejemplo esta duración se establece en 5 segundos. Para completar la animación agregaramos algunas propiedades al elemento HTML:

div#animado {
    -webkit-animation-name: ejemplo;    
    animation-name: ejemplo;
    -webkit-animation-duration: 5s;
    animation-duration: 5s;    
    position: absolute;    
    left: 0;
    top: 0;
}

Aparecen dos propiedades animation-name y animation-duration, ambas agregando el prefijo -webkit- para Chrome 28 y Safari 5.1.7. El resto de navegadores Firefox 22, Opera 12.16 y IE10 ya lo soportan sin prefijo. La animación comienza a ejecutarse cuando el navegador identifica animation-name con un conjunto de marcos del mismo nombre. Por lo tanto la animación se inicia con la carga de estilo al cargarse la página. En el inicio de la secuencia el elemento estará en la posición (0, 0) y al finalizar la secuencia volverá a esa posición. Esa posición coincide con la establecida en el elemento antes de iniciarse la animación, como puede observarse en las propiedades left y top del código anterior. Pero de no ser así esos valores previos a la animación serían repuestos al finalizar la misma. Es decir, una animación modifica los valores previos al inicio durante la ejecución, pero al finalizar los repone otra vez.

Aunque el trozo de CSS anterior ejecutaría la animación con la carga de la página, en el ejemplo anterior he optado por acompañar un botón e iniciar la animación con un script:

<style>
    ...
    div#animado {
        position: absolute;
        /* Si ponemos esto la animación se ejecutará con la carga de la página...
        -webkit-animation-name: ejemplo;
        animation-name: ejemplo; */
        -webkit-animation-duration: 5s;
        animation-duration: 5s;
        left: 0;
        top: 0;
    }
    /* ...pero entonces no hace falta poner esta clase, que es sólo para ejecutar
    la animación con un botón y usando JavaScript (ver código siguiente) */
    div.clase-animado {
        -webkit-animation-name: ejemplo;
        animation-name: ejemplo;
    }
    ...
</style>

<script>
    ...
    wxL.listenerBotonAnimar = function() {
        var animado = document.getElementById("animado");
        animado.className = "";
        window.setTimeout(function(){
            animado.className = "clase-animado";
        }, 1);
    };
    ...
</script>

El elemento con id="animado" no tiene declarado inicialmente la propiedad animation-name ni el nombre de clase class="clase-animado". Su código HTML es simplemente <div id="animado">ANIMADO</div>. Luego con un script le ponemos ese nombre de clase y eso hará que se ejecute el estilo de la regla div.clase-animado {...} donde animation-name recibe el valor ejemplo, desencadenándose la animación del conjunto de marcos del mismo nombre.

Para que el manejador del botón pueda ejecutar la animación la segunda y siguientes veces, previamente borraremos el nombre de la clase y volveremos a establecer class="clase-animado". Para que funcione podemos lanzar una tarea setTimeout para que el navegador pueda detectar el cambio en el nombre de la clase. Aunque hay otras formas de iniciar una animación como veremos en un apartado más abajo.

Por último hay que apuntar que las propiedades CSS a las que se les puede aplicar animación son aquellas con la característica de animables (o capaces de ser animadas, animatable en la especificación en inglés). Son las mismas que las usadas en las transiciones.

Propiedades de animación

En el ejemplo anterior sólo hacíamos uso de las propiedades animation-name y animation-duration. El resto de propiedades que no se declaran toman sus valores predeterminados o iniciales que son los que figuran en el siguiente ejemplo:

Ejemplo:

Código completo de este ejemplo

Animamos cuatro elementos <div> con estilo para darle forma de triángulos igual que el logo de este sitio. Usamos vpForCss, un gestor de vendor prefixes para manejar las propiedades de estilo de animación con mayor comodidad ya que algunos navegadores aún necesitan prefijo. El logo (los cuatro triángulos) están ubicados inicialmente en la posición (0, 0) en la esquina superior izquierda. Cuando se inicia la animación con dirección normal, los cuatro triángulos se ubican por fuera del contenedor con borde gris, aunque no se verán pues este contenedor tiene overflow con valor hidden. Luego avanzan hacia el centro del mismo. Al finalizar la animación vuelven a la posición inicial en la esquina superior izquierda. Resumimos las propiedades de animación:

  • Ciclos o repeticiones (animation-iteration-count): Es un número entero mayor o igual a 0 o la palabra clave infinite. Son los ciclos de la animación. Con infinite se repiten indefinidamente. El valor inicial es 1 ciclo.
  • Dirección (animation-direction): Admite los valores siguientes
    • normal: Los ciclos son ejectuados tal y como se declaran en el conjunto de marcos keyframes.
    • reverse: Los ciclos son ejecutados en dirección inversa a como fueron declarados.
    • alternate: Los ciclos pares se ejecutan con valor normal y los impares con valor reverse.
    • alternate-reverse: Los ciclos pares se ejecutan con valor reverse y los impares con valor normal.
    Esto se puede probar y ver más fácilmente usando el valor infinite para animation-iteration-count. El valor inicial es normal.
  • Duración (animation-duration): El tiempo en segundos necesario para completar un ciclo. Valor inicial 1s (1 segundo).
  • Función de temporización (animation-timing-function): Describe como progresa la animación durante el tiempo que dura un ciclo. Puede ver más sobre esto en Transiciones CSS3, transition-timing-function pues se usan las mismas funciones de temporización. Valor inicial ease.
  • Demora (animation-delay): Demora inicial en segundos antes de iniciar la animación. Admite también valores negativos, de tal forma que cuando se inicie aparecerá en un punto como si se hubiese iniciado anteriormente. Valor inicial 0s. Puede probar en el ejemplo estableciendo una duración de 4 segundos y una demora de -2 segundos, con valores iniciales para el resto.
  • Estado ejecución (animation-play-state): Admite los valores running (ejecutándose) y paused (pausado). El valor inicial es running. Para probarlo use infinite para animation-iteration-count y el resto con valores iniciales. Después de ejecutar cambie el valor del estado de ejecución a paused y comprobará que la animación se detiene. Reponiendo el valor running reanudamos la ejecución en ese punto.
  • Modo de relleno (animation-fill-mode): Define que valores serán aplicados antes y después del tiempo de ejecución. Por defecto una animación no afectará a los valores de la propiedad objeto de animación en el tiempo de demora previo al inicio ni después de su finalización. Pero el modo de relleno puede cambiar este comportamiento.
    • Con none, valor inicial, no se aplica ningún cambio.
    • Con backwards se aplican los valores desde el inicio del periodo de demora en el marco que primero se ejecute (from para dirección normal o alternate y to para dirección reverse o alternate-reverse). Esto se observa en el ejemplo poniendo una demora de 1 segundo y el resto de valores iniciales (valor none para el modo de relleno). El logo permanece en la esquina superior izquierda (su posición inicial) durante ese segundo, tras lo cual se vacía el contenedor y luego se inicia la animación apareciendo los cuatro triángulos por cada uno de los lados del contenedor. Pero si usamos el valor backwards para el modo de relleno, desde que se inicia el tiempo de demora ya los cuatro triángulos desaparecen, pues se aplican los from de los marcos de la animación que posicionan los triángulos por fuera del contenedor (tiene overflow con valor hidden). Luego pasa ese tiempo de demora y entonces se inicia la animación avanzando los triángulos hacia el centro.
    • Con forwards después de que la animación termine aplicará los valores de la propiedad que tiene en el momento de finalizar. En el ejemplo veremos que esa posición final será (50, 50), centrado en el contenedor. Cuando lleguen a ese punto presentamos un mensaje deteniendo la ejecución para poder comprobarlo, pues posteriormente pararemos la animación con animation-name="none" lo que causará que el elemento recupere sus valores iniciales (sin animación), valores que ubican el elemento en la posición (0, 0), esquina superior izquierda. El uso de animation-name para ejecutar repetidas veces una animación lo comentaremos a continuación.

Iniciar y parar animaciones

La propiedad animation-play-state nos permite pausar una animación y reanudarla con los valores paused y running. Pero si una animación con un número de ciclos definido finaliza por sí sola, no podremos volver a iniciarla actuando sólo con esta propiedad.

Para que una animación se inicie es necesario que exista un marco keyframes con el mismo nombre que el valor de la propiedad animation-name de un elemento. La animación se iniciará cuando el navegador resuelva el estilo o cargue la página, lo que ocurra más tarde. Si la página está ya cargada pero animation-name tiene el valor inicial none y luego modificamos con JavaScript ese valor al de un marco existente, la animación se iniciará. O bien aplicando ese cambio en el valor de animation-name en una regla con la pseudo-clase :hover y la activemos, como en el siguiente ejemplo que se activa al situar el cursor sobre la imagen del logo de este sitio:

Ejemplo:

Logo Wextensible wextensible

Código completo de este ejemplo

En este ejemplo el valor de animation-name se aplica en las pseudo-clases img:hover y img:hover + span que animan el logo y un elemento contiguo con el texto. Al desaparecer la actuación de :hover el valor de animation-name recobra su valor inicial none y por lo tanto cuando activemos :hover otra vez se reiniciará la animación. En cualquier caso animation-name tiene que cambiar a un valor de un keyframes existente. Una vez que una animación empieza continuará hasta que finalice o el valor de animation-name sea puesto a none (su valor inicial). Esta idea general también nos sirve para parar una animación en ejecución sin usar :hover.

En el ejemplo del primer apartado el keyframes se llamaba ejemplo. Había una regla div.clase-animado para declarar animation-name: ejemplo, pero el elemento no tenía un atributo class="clase-animado". Con JavaScript podíamos poner y quitar ese nombre de clase para, primero parar la animación, y luego reiniciarla. Al adjudicar un nombre de clase con JavaScript se adjudican los estilos declarados y por tanto se resuelve la pareja animation-name="ejemplo" con @keyframes ejemplo y se inicia la animación.

En el ejemplo del segundo apartado iniciábamos la animación mediante JavaScript asignando animation-name = "mover-triangulo-top" y right, bottom, left para los otros triángulos. Dado que los keyframes tenían esos nombres, en ese momento se iniciaba la animación. Al finalizar o cuando pulsábamos el botón de parar, deteníamos la animación haciendo animation-name="none", de tal forma que luego podíamos reiniciarla otra vez dándole de nuevo los nombres de los keyframes. Pero cuando detenemos así una animación se pierden los valores finales de la posición del último marco, tal como podíamos comprobar usando la propiedad animation-fill-mode con el valor forwards.

Por último aplicando el estilo display con el valor none hará que una animación finalice. Si entonces restauramos a un valor distinto de none hará que se inicie otra vez. Esta es otra forma de parar y volver a iniciar una animación, pero ocultaremos el elemento tras la parada.

Eventos de animaciones

Hay tres eventos que podemos usar para controlar las animaciones. Los nuevos atributos específicos para estos eventos son:

  • animationName nos da el valor de la propiedad de estilo animation-name que ha disparado el evento.
  • elapsedTime nos da la cantidad de segundos que lleva la animación ejecutándose, excluyendo los tiempos durante los que hubiese estado pausada. Si hay -d segundos de demora negativa declarados en la propiedad animation-delay, inicialmente al dispararse el evento tomará el valor -1×d segundos.
  • pseudoElement dará el nombre de un pseudo-elemento CSS si la animación se inició ahí, o una cadena vacía en caso de iniciarse en un elemento HTML normal. Como cualquier otro evento, podemos acceder a ese elemento con la propiedad target.

Los tipos de eventos disponibles son:

  • animationstart ocurre cuando se inicia la animación. Si hay demora, el evento se ejecuta al finalizar ese periodo de demora.
  • animationend, ocurre al finalizar la animación.
  • animationiteration, ocurre al finalizar cada ciclo, excepto si si dispara también animationend. Por lo tanto sólo ocurrirán con los primeros ciclos pero no en el último. Así una animación de un único ciclo no ejecutará este evento.

En el ejemplo del segundo apartado hemos usado animationend para detectar cuando ha finalizado una animación y poner animation-name al valor none, lo que nos permitirá reiniciarla posteriormente como vimos en el apartado anterior.

<script>
    ...
    window.onload = function(){
        ...
            //Evento en el triángulo izquierda que se ejecuta cuando finalice la
            //animación, cambiando el título del botón de "Parar" a "Ejecutar". CH 28
            //y SA 5.1.7 necesita webkitAnimationEnd, el resto (FF 22, OP 12.16, IE 11)
            //no necesitan prefijo.
            var evento = "AnimationEnd";
            if (prefijo=="") evento = evento.toLowerCase();
            document.getElementById("triangulo-left").addEventListener(prefijo + evento,
                    wxL.listenerAnimationEnd, false);
        ...

    };

    ...

    wxL.listenerAnimationEnd = function() {
        document.getElementById("boton-ejecutar").value = "Ejecutar";
        wxL.animar("parar");
    };

    ...

    wxL.animar = function(accion){
        var triangulos = document.getElementsByClassName("triangulo");
        if (accion=="mover"){
            for (var i=0, maxi=triangulos.length; i<maxi; i++) {
                ...
                triangulos[i].style[wxG.vpForCss["animation-name"]] = accion + "-" + triangulos[i].id;

            }
        } else { //parar
            ...
            for (var i=0, maxi=triangulos.length; i<maxi; i++) {
                triangulos[i].style[wxG.vpForCss["animation-name"]] = "none";
            }
        }
    };

</script>

El código anterior sólo contiene lo relacionado con el evento, suprimiendo con puntos suspensivos otras cosas. En el window.onload declaramos el evento animationend. Pero algunos navegadores como Chrome 28 necesitan prefijo siendo webkitAnimationEnd. El prefijo lo hemos recuperado en un paso previo usando el gestor vpForCss mencionado más arriba. Si las animaciones son soportadas sin prefijo, este será una cadena vacía y convertimos AnimationEnd a minúsculas. Luego agregamos el evento con addEventListener. Aunque se animan cuatro elementos, el orden es tal que el último en la animación es el triángulo de la izquierda ("triangulo-left"). Por lo tanto se disparará el evento cuando este triángulo finalice su animación.

El manejador del evento es wxL.listenerAnimationEnd. Cambia el título del botón de "Parar" a "Ejecutar", para dejarlo preparado para una posible ejecución posterior. Luego llama a wxL.animar("parar"). Esa función, aparte de iniciar la animación aplicando las propiedades y luego actualizando animation-name, en este caso al dispararse el evento animationend también parará la animación poniendo none en animation-name lo que nos permitirá reiniciarla posteriormente.


Y hasta aquí esta introducción a las animaciones CSS3, creo que suficiente para empezar a entenderlas y usarlas.