Control de reproducción de animaciones web en Chrome 39

A principios de este año, Chrome 36 introdujo el método element.animate como parte de las especificaciones de animaciones web más amplias. Esto permite crear animaciones nativas eficientes escritas 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 repasar rápidamente, aquí te mostramos cómo puedes animar una nube en la pantalla, con una devolución de llamada cuando finalices:

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 tenerlo en cuenta como parte de tu caja de herramientas cuando compilas 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 adiciones de reproducción abren las posibilidades de lo que Web Animations puede hacer: convertir animaciones en una herramienta de uso general, en lugar de ser prescriptivas sobre 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() hace que la animación en ejecución finalice efectivamente, currentTime también se invierte.Por ejemplo, si se invierte una animación completamente nueva, toda la animación se reproducirá hacia atrás.

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

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 externamente, quizás 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 podría combinarse con el comportamiento de inversión, según el lugar desde el que se quitó el mouse de la página (demostración combinada).

En lugar de borrar una AnimationPlayer en respuesta a una interacción del usuario, su currentTime también se podría 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 que se establezca un valor y que la implementación nativa subyacente se encargue de la visualización del progreso. En el caso de descarga, la duración de una animación se podría establecer en el tamaño de descarga total y el currentTime en el tamaño de descarga actual (demostración).

Transiciones y gestos de la IU

Las plataformas móviles han sido durante mucho tiempo el dominio de los gestos comunes: arrastrar, deslizar, arrastrar y demás. 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 "desvío". 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 su orden de creación: en este caso, driftPlayer tendrá prioridad sobre el reproductor. Cuando se complete driftPlayer, desaparecerán junto con sus efectos. Sin embargo, su hora final coincidirá con el tiempo actual del jugador subyacente, por lo que tu IU se mantendrá coherente.

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

Adelante y element.animate.

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

Estas dos funciones también son totalmente compatibles con otros navegadores modernos mediante un polyfill liviano. Este polyfill también realiza una detección de funciones, por lo que, a medida que los proveedores de navegadores implementen la especificación, mejorará y acelerada cada vez más 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.