Wextensible

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:

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:

Los tipos de eventos disponibles son:

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.