Control de reproducción de animaciones web en Chrome 39

A principios de este año, Chrome 36 lanzó el método element.animate como parte de la especificación de animaciones web más amplia. Esto permite animaciones nativas y eficientes escritas de forma imperativa, lo que les brinda a los desarrolladores la opción de compilar sus animaciones y transiciones con el enfoque más adecuado para ellos.

Para hacer un repaso rápido, aquí te mostramos cómo puedes animar una nube en la pantalla, con una devolución de llamada cuando termines:

var player = cloud.animate([
    {transform: 'translateX(' + start + 'px)'},
    {transform: 'translateX(' + end + 'px)'}
], 5000);
player.onfinish = function() {
    console.info('Cloud moved across the screen!');
    startRaining(cloud);
};

Esto es increíblemente fácil y vale la pena considerarlo como parte de tu caja de herramientas cuando crees animaciones o transiciones de manera imperativa. Sin embargo, en Chrome 39, se agregaron funciones de control de reproducción al objeto AnimationPlayer que muestra element.animate. Anteriormente, una vez que se creaba una animación, solo se podía llamar a cancel() o escuchar el evento de finalización.

Estas incorporaciones de reproducción amplían las posibilidades de lo que pueden hacer las animaciones web: convertirlas en una herramienta de uso general, en lugar de ser prescriptivas sobre las transiciones, es decir, animaciones "fijas" o predefinidas.

Pausar, retroceder o cambiar la velocidad de reproducción

Comencemos por actualizar el ejemplo anterior para pausar la animación si se hace clic en la nube:

cloud.addEventListener('mousedown', function() {
    player.pause();
});

También puedes modificar la propiedad playbackRate:

function changeWindSpeed() {
    player.playbackRate *= (Math.random() * 2.0);
}

También puedes llamar al método reverse(), que normalmente equivale a invertir el playbackRate actual (multiplica por -1). Sin embargo, existen algunos casos especiales:

  • Si el cambio causado por el método reverse() provocara que la animación en ejecución finalice de manera efectiva, el currentTime también se invierte (p. ej., si se invierte una animación nueva, toda la animación se reproducirá al revés).

  • Si el reproductor está en pausa, se comenzará a reproducir la animación.

Arrastrar el reproductor

Un AnimationPlayer ahora permite que se modifique su currentTime mientras se ejecuta una animación. Por lo general, este valor aumentará con el tiempo (o disminuirá si el playbackRate es negativo). Esto podría permitir que la posición de una animación se controle de forma externa, tal vez a través de la interacción del usuario. Esto se conoce comúnmente como limpieza.

Por ejemplo, si tu página HTML representaba el cielo y deseas realizar un gesto de arrastre para cambiar la posición de una nube que se está reproduciendo, podrías agregar algunos controladores al documento:

var startEvent, startEventTime;
document.addEventListener('touchstart', function(event) {
    startEvent = event;
    startEventTime = players.currentTime;
    player.pause();
});
document.addEventListener('touchmove', function(event) {
    if (!startEvent) return;
    var delta = startEvent.touches[0].screenX -
        event.changedTouches[0].screenX;
    player.currentTime = startEventTime + delta;
});

A medida que arrastres el documento, se modificará el valor de currentTime para reflejar la distancia del evento original. También puedes reanudar la reproducción de la animación cuando finalice el gesto:

document.addEventListener('touchend', function(event) {
    startEvent = null;
    player.play();
});

Esto incluso se puede combinar con el comportamiento de inversión, según el lugar desde el que se levantó el mouse de la página (demostración combinada).

En lugar de borrar un AnimationPlayer en respuesta a una interacción del usuario, su currentTime también se puede usar para mostrar el progreso o el estado, por ejemplo, para mostrar el estado de una descarga.

La utilidad aquí es que un AnimationPlayer permite establecer un valor y que la implementación nativa subyacente se encargue de su visualización de progreso. En el caso de la descarga, la duración de una animación se puede establecer en el tamaño total de descarga y el currentTime en el tamaño descargado actualmente (demo).

Transiciones y gestos de la IU

Las plataformas para dispositivos móviles han sido durante mucho tiempo el reino de los gestos comunes: arrastrar, deslizar, lanzar y similares. Estos gestos suelen tener un tema común: un componente de IU arrastrable, como "tirar para actualizar" de una vista de lista o una barra lateral que se desplaza desde el lado izquierdo de la pantalla.

Con Web Animations, es muy fácil replicar un efecto similar aquí en la Web, en computadoras de escritorio o en dispositivos móviles. Por ejemplo, cuando se completa un gesto que controla currentTime:

var steps = [ /* animation steps */ ];
var duration = 1000;
var player = target.animate(steps, duration);
player.pause();
configureStartMoveListeners(player);

var setpoints = [0, 500, 1000];
document.addEventListener('touchend', function(event) {
    var srcTime = player.currentTime;
    var dstTime = findNearest(setpoints, srcTime);
    var driftDuration = dstTime - srcTime;

    if (!driftDuration) {
    runCallback(dstTime);
    return;
    }

    var driftPlayer = target.animate(steps, {
    duration: duration,
    iterationStart: Math.min(srcTime, dstTime) / duration,
    iterations: Math.abs(driftDuration) / duration,
    playbackRate: Math.sign(driftDuration)
    });
    driftPlayer.onfinish = function() { runCallback(dstTime); };
    player.currentTime = dstTime;
});

Esto crea una animación adicional que realiza un "desplazamiento". Esto se reproduce entre el lugar donde se completó el gesto y hasta nuestro buen objetivo conocido.

Esto funciona porque las animaciones tienen una prioridad según el orden en que se crean: en este caso, driftPlayer tendrá prioridad sobre el jugador. Cuando se complete driftPlayer, desaparecerá junto con sus efectos. Sin embargo, su hora final coincidirá con el currentTime del jugador subyacente, por lo que tu IU seguirá siendo coherente.

Por último, si te gustan los gatitos, hay una aplicación web de demostración que muestra estos gestos. Es compatible con dispositivos móviles y usa el polyfill para la retrocompatibilidad, así que intenta cargarlo en tu dispositivo móvil.

Continúa con element.animate

El método element.animate es lo mejor en este momento, ya sea que lo uses para animaciones simples o aproveches su AnimationPlayer que se muestra de otras maneras.

Estas dos funciones también son totalmente compatibles con otros navegadores modernos a través de un polyfill ligero. Este polyfill también realiza la detección de funciones, por lo que, a medida que los proveedores de navegadores implementen la especificación, esta función solo se volverá más rápida y mejor con el tiempo.

La especificación de Web Animations también seguirá evolucionando. Si te interesa explorar las próximas funciones, también están disponibles en un polyfill más detallado: web-animations-next.