Mejoras de la API de Web Animations en Chromium 84

Orquestación de animaciones con promesas, mejoras de rendimiento con animaciones reemplazables, animaciones más fluidas con modos compuestos y mucho más.

Fecha de publicación: 27 de mayo de 2020

Cuando se usan correctamente, las animaciones mejoran la percepción y la memoria de tu marca, guían las acciones de los usuarios y los ayudan a navegar por tu aplicación, lo que proporciona contexto en un espacio digital.

La API de Web Animations es una herramienta que permite a los desarrolladores escribir animaciones imperativas con JavaScript. Se escribió para respaldar las implementaciones de animación y transición de CSS y permitir que se desarrollen efectos futuros, así como que se compongan y se programen los efectos existentes.

Si bien Firefox y Safari ya implementaron el conjunto completo de funciones de las especificaciones, Chromium 84 agrega una gran cantidad de funciones que antes no se admitían en Chrome y Edge, lo que permite la interoperabilidad entre navegadores.

La API de animaciones web llegó por primera vez a Chromium en la versión 36, en julio de 2014. Ahora, la especificación se completará en la versión 84, que se lanzará en julio de 2020.
La larga historia de la API de Web Animations en Chromium.

Cómo comenzar

Crear una animación con la API de Web Animations debería resultarte muy familiar si usaste reglas @keyframe. Primero, debes crear un objeto de fotogramas clave. Podría verse así en CSS:

@keyframes openAnimation {
  0% {
    transform: scale(0);
  }
  100% {
    transform: scale(1);
  }
}

se vería así en JavaScript:

const openAnimation = [
  { transform: 'scale(0)' },
  { transform: 'scale(1)' },
];

Dónde estableces los parámetros para la animación en CSS:

.modal {
  animation: openAnimation 1s 1 ease-in;
}

que configurarías en JS:

document.querySelector('.modal').animate(
    openAnimation, {
      duration: 1000, // 1s
      iterations: 1, // single iteration
      easing: 'ease-in' // easing function
    }
);

La cantidad de código es aproximadamente la misma, pero con JavaScript, obtienes algunas funciones adicionales que no tienes solo con CSS. Esto incluye la capacidad de secuenciar efectos y un mayor control de sus estados de reproducción.

Más allá de element.animate()

Sin embargo, con la actualización, la API de Web Animations ya no se limita a las animaciones creadas con element.animate(). También podemos manipular las animaciones y transiciones de CSS.

getAnimations() es un método que muestra todas las animaciones de un elemento, independientemente de si se creó con element.animate() o con reglas de CSS (animación o transición de CSS). Este es un ejemplo de cómo se ve:

Primero, "get" los fotogramas clave de la transición para determinar de dónde estamos haciendo la transición. Luego, creas dos animaciones de opacidad nuevas, lo que habilita el efecto de atenuación cruzada. Una vez que se complete la transición, borra la copia.

Cómo organizar animaciones con promesas

En Chromium 84, ahora tienes dos métodos que se pueden usar con promesas: animation.ready y animation.finished.

  • animation.ready te permite esperar a que se apliquen los cambios pendientes (es decir, cambiar entre métodos de control de reproducción, como reproducir y pausar).
  • animation.finished proporciona un medio para ejecutar código JavaScript personalizado cuando se completa una animación.

Continuando con nuestro ejemplo, crea una cadena de animación orquestada con animation.finished. Aquí, tienes una transformación vertical (scaleY), seguida de una transformación horizontal (scaleX) y, luego, un cambio de opacidad en un elemento secundario:

Aplicación de transformaciones y opacidad a un elemento modal de apertura. Ver demostración en Codepen
const transformAnimation = modal.animate(openModal, openModalSettings);
transformAnimation.finished.then(() => { text.animate(fadeIn, fadeInSettings)});

Encadenamos estas animaciones con animation.finished.then() antes de ejecutar el siguiente conjunto de animaciones en la cadena. De esta manera, las animaciones aparecen en orden y hasta aplicas efectos a diferentes elementos de destino con diferentes opciones configuradas (como velocidad y facilidad).

En CSS, sería engorroso volver a crear esto, en especial cuando se aplican animaciones únicas, pero secuenciadas, a varios elementos. Deberás usar un @keyframe, ordenar los porcentajes de tiempo correctos para colocar las animaciones y usar animation-delay antes de activar las animaciones en la secuencia.

Ejemplo: Reproducir, pausar y revertir

Lo que se puede abrir, debe cerrarse. Por suerte, desde Chromium 39, la API de Web Animations nos permite reproducir, pausar y revertir nuestras animaciones.

Puedes tomar la animación que se mostró anteriormente y darle una animación suave y revertida cuando vuelvas a hacer clic en el botón con .reverse(). De esta manera, puedes crear una interacción más fluida y contextual para nuestro elemento modal.

Un ejemplo de una ventana modal que se abre y se cierra cuando se hace clic en un botón. Ver demostración en Glitch

Lo que puedes hacer es crear dos animaciones pendientes de reproducción (openModal y una transformación de opacidad intercalada) y, luego, pausar una de las animaciones y retrasarla hasta que termine la otra. Luego, puedes usar promesas para esperar a que cada una termine antes de reproducir. Por último, puedes verificar si se configuró una marca y, luego, invertir cada animación.

Ejemplo: Interacciones dinámicas con fotogramas clave parciales

Ejemplo de reorientación, en el que un clic del mouse ajusta la animación a una ubicación nueva. Ver demostración en Glitch
selector.animate([{transform: `translate(${x}px, ${y}px)`}],
    {duration: 1000, fill: 'forwards'});

En este ejemplo, solo hay un fotograma clave y no se especifica una posición de inicio. Este es un ejemplo del uso de fotogramas clave parciales. El controlador del mouse hace algunas acciones: establece una nueva ubicación de destino y activa una nueva animación. La nueva posición de inicio se infiere de la posición subyacente actual.

Las transiciones nuevas se pueden activar mientras las existentes aún se están ejecutando. Esto significa que se interrumpe la transición actual y se crea una nueva.

Mejoras de rendimiento con animaciones reemplazables

Cuando se crean animaciones basadas en eventos, como en 'mousemove', se crea una animación nueva cada vez, lo que puede consumir memoria rápidamente y degradar el rendimiento. Para abordar este problema, se introdujeron animaciones reemplazables en Chromium 83, lo que permite la limpieza automática, en la que las animaciones terminadas se marcan como reemplazables y se quitan automáticamente si se reemplazan por otra animación terminada. Consulta el siguiente ejemplo:

Se anima una estela de cometa cuando se mueve el mouse. Ver demostración en Glitch
elem.addEventListener('mousemove', evt => {
  rectangle.animate(
    { transform: translate(${evt.clientX}px, ${evt.clientY}px) },
    { duration: 500, fill: 'forwards' }
  );
});

Cada vez que se mueve el mouse, el navegador vuelve a calcular la posición de cada bola en la estela del cometa y crea una animación para este nuevo punto. El navegador ahora sabe quitar las animaciones anteriores (habilita el reemplazo) en los siguientes casos:

  1. La animación terminó.
  2. Hay una o más animaciones más altas en el orden compuesto que también están terminadas.
  3. Las animaciones nuevas animan las mismas propiedades.

Para ver exactamente cuántas animaciones se reemplazan, puedes sumar un contador con cada animación quitada y usar anim.onremove para activarlo.

Existen algunas propiedades y métodos adicionales para llevar tu control de animación aún más lejos:

  • animation.replaceState proporciona un medio para hacer un seguimiento de si una animación está activa, persiste o se quita.
  • animation.commitStyles() actualiza el estilo de un elemento según el estilo subyacente junto con todas las animaciones del elemento en el orden compuesto.
  • animation.persist() marca una animación como no reemplazable.

Animaciones más fluidas con modos compuestos

Con la API de Web Animations, ahora puedes establecer el modo compuesto de tus animaciones, lo que significa que pueden ser aditivos o acumulativos, además del modo predeterminado de "reemplazo". Los modos compuestos permiten a los desarrolladores escribir animaciones distintas y tener control sobre cómo se combinan los efectos. Ahora se admiten tres modos compuestos: 'replace' (el modo predeterminado), 'add' y 'accumulate'.

Cuando se combinan animaciones, un desarrollador puede escribir efectos cortos y distintos, y verlos combinados. En el siguiente ejemplo, aplicamos una keyframe de rotación y escala a cada cuadro, y el único ajuste es el modo compuesto, que se agrega como opción:

Una demostración que muestra los modos compuestos predeterminado, agregado y acumulado. Ver demostración en Glitch

En el modo compuesto 'replace' predeterminado, la animación final reemplaza la propiedad de transformación y termina en rotate(360deg) scale(1.4). Para 'add', el compuesto agrega la rotación y multiplica la escala, lo que genera un estado final de rotate(720deg) scale(1.96). 'accumulate' combina las transformaciones, lo que genera rotate(720deg) scale(1.8). Para obtener más información sobre las complejidades de estos modos compuestos, consulta Las enumeraciones CompositeOperation y CompositeOperationOrAuto de la especificación de animaciones web.

Observa el siguiente ejemplo de elemento de la IU:

Un menú desplegable con movimiento que tiene dos animaciones compuestas aplicadas. Ver demostración en Glitch

Aquí, se combinan dos animaciones top. La primera es una macroanimación, que mueve el menú desplegable por toda la altura del menú como un efecto de deslizamiento desde la parte superior de la página, y la segunda, una microanimación, aplica un pequeño rebote cuando llega a la parte inferior. El uso del modo compuesto 'add' permite una transición más fluida.

const dropDown = menu.animate(
    [
      { top: `${-menuHeight}px`, easing: 'ease-in' },
      { top: 0 }
    ], { duration: 300, fill: 'forwards' });

  dropDown.finished.then(() => {
    const bounce = menu.animate(
      [
        { top: '0px', easing: 'ease-in' },
        { top: '10px', easing: 'ease-out' },
        { ... }
      ], { duration: 300, composite: 'add' });
  });

Novedades de la API de Web Animations

Todas estas son incorporaciones emocionantes a las capacidades de animación de los navegadores actuales, y aún hay más en camino. Consulta estas especificaciones futuras para obtener más información sobre lo que sucederá a continuación: