Control de reproducción de animaciones web en Chrome 39

A principios de este año, Chrome 36 incorporó el método element.animate como parte de la especificación de Web Animations más amplia. Esto permite que se escriban animaciones nativas eficaces de forma imperativa, lo que les da a los desarrolladores la opción de crear sus animaciones y transiciones con el enfoque más adecuado para ellas.

Para repasar rápidamente, así es como puedes animar una nube en la pantalla, con una devolución de llamada cuando hayas terminado:

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 por sí solo es increíblemente fácil y vale la pena considerarlo como parte de tu caja de herramientas cuando compiles 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 podías llamar a cancel() o escuchar el evento de finalización.

Estas adiciones de reproducción abren las posibilidades de lo que pueden hacer las animaciones web: convertir las animaciones en una herramienta de uso general, en lugar de ser prescriptivas con respecto a 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 la playbackRate actual (multiplica por -1). Sin embargo, hay algunos casos especiales:

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

  • Si se pausa el reproductor, la animación comenzará a reproducirse.

Cómo arrastrar el reproductor

Un elemento 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 playbackRate es negativo). Esto podría permitir que la posición de una animación se controle externamente, quizás a través de la interacción del usuario. Por lo general, esto se conoce como depuración.

Por ejemplo, si tu página HTML representa el cielo y quieres que se realice un gesto de arrastre para cambiar la posición de una nube que se está reproduciendo actualmente, puedes 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 cambiará el currentTime para reflejar la distancia de tu 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 podría combinarse con la inversión del comportamiento, en función del lugar desde donde se levantó el mouse de la página (demostración combinada).

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

La utilidad es que un AnimationPlayer permite establecer un valor y hacer 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 podría establecer en el tamaño de descarga total y currentTime en el tamaño descargado actualmente (demostración).

Gestos y transiciones de la IU

Desde hace mucho tiempo, las plataformas móviles han sido el dominio de los gestos comunes: arrastrar, deslizar, arrastrar y soltar, entre otros. Estos gestos suelen tener un tema en común: un componente de IU arrastrable, como "deslizar para actualizar" de una vista de lista o una barra lateral que se genera desde el lado izquierdo de la pantalla.

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

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 “desvío”. Esto se reproduce desde el momento en que se completó el gesto hasta el objetivo conocido como bueno.

Esto funciona porque las animaciones tienen una prioridad según su orden de creación: en este caso, driftPlayer tendrá prioridad sobre el reproductor. Cuando driftPlayer se complete, desaparecerán, junto con sus efectos. Sin embargo, el último horario coincidirá con el currentTime del jugador subyacente, por lo que la IU permanecerá coherente.

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

Ve y anima los elementos

El método element.animate es excelente 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, con un polyfill ligero. Este polyfill también realiza detección de funciones, por lo que, a medida que los proveedores de navegadores implementen la especificación, la función se volverá más rápida y mejorada con el tiempo.

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