Mejoras de la API de Web Animations en Chromium 84

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

Kevin Ellis
Kevin Ellis

Cuando se usan correctamente, las animaciones mejoran la percepción y la memoria del usuario de tu marca, guían sus acciones 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. Está escrito para respaldar las implementaciones de animación y transición de CSS y permitir el desarrollo de efectos futuros y de la composición y el tiempo de los efectos existentes.

Si bien Firefox y Safari ya implementaron el conjunto completo de funciones de especificaciones, Chromium 84 ofrece una variedad de funciones que antes no eran compatibles con Chrome y Edge, lo que permite la interoperabilidad entre navegadores.

La API de Web Animations se lanzó por primera vez a Chromium en su versión 36, julio de 2014. Ahora, estará completa la especificación en la versión 84, con el lanzamiento en julio de 2020.
La larga historia de la API de Web Animations en Chromium.

Primeros pasos

La creación de una animación a través de la API de Web Animations debería resultarte muy familiar si usaste reglas @keyframe. Primero, debes crear un objeto de fotograma clave. Así es como 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)' },
];

Cuando estableces parámetros para la animación en CSS:

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

que establecerí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 casi la misma, pero con JavaScript, obtienes un par de superpoderes que no se tienen solo con CSS. Esto incluye la capacidad de secuenciar efectos y un mayor control de sus estados de juego.

Después de element.animate()

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

getAnimations() es un método que muestra todas las animaciones en un elemento, sin importar si se creó a través de element.animate() o mediante reglas de CSS (animación o transición CSS). Este es un ejemplo de cómo se ve:

Primero, "get" los fotogramas clave de la transición para determinar desde dónde estamos haciendo la transición. A continuación, crearás dos nuevas animaciones de opacidad, que permitirán el efecto de atenuación de cruz. Una vez que se complete el encadenado, borrarás 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 los 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.

Continuemos con nuestro ejemplo y creemos una cadena de animación organizada 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 e incluso aplicas efectos a diferentes elementos objetivo con diferentes opciones configuradas (como velocidad y facilidad).

Dentro de CSS, esto sería engorroso de recrear, en especial cuando se aplican animaciones únicas, pero secuenciadas, a varios elementos. Deberás usar un @keyframe, clasificar los porcentajes de sincronización 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. Afortunadamente, desde Chromium 39, la API de Web Animations nos brindó la capacidad de reproducir, pausar y revertir nuestras animaciones.

Puedes tomar la animación anterior y darle una animación invertida y fluida cuando vuelvas a hacer clic en el botón con .reverse(). De esta manera, podrás crear una interacción más fluida y contextual para nuestra ventana modal.

Ejemplo de una apertura y un cierre modal cuando se hace clic en un botón. Ver demostración de Glitch

Lo que puedes hacer es crear dos animaciones de reproducción pendiente (openModal y una transformación de opacidad intercalada) y, luego, pausar una de las animaciones y demorarla hasta que la otra haya terminado. Luego, puedes usar promesas para esperar a que cada uno finalice antes de jugar. Por último, puedes verificar si se configuró una marca y, luego, revertir cada animación.

Ejemplo: Interacciones dinámicas con fotogramas clave parciales

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

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

Las transiciones nuevas se pueden activar mientras se ejecutan las existentes. Esto significa que se interrumpe la transición actual y se crea una nueva.

Mejoras en el rendimiento con animaciones reemplazables

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

Cuando se mueve el mouse, se anima un rastro de cometas. Ver demostración de Glitch
elem.addEventListener('mousemove', evt => {
  rectangle.animate(
    { transform: translate(${evt.clientX}px, ${evt.clientY}px) },
    { duration: 500, fill: 'forwards' }
  );
});

Cada vez que el mouse se mueve, el navegador vuelve a calcular la posición de cada bola en el rastro del cometa y crea una animación para este nuevo punto. El navegador ahora sabrá que debe quitar las animaciones antiguas (habilitando el reemplazo) cuando:

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

Puedes ver exactamente cuántas animaciones se reemplazan si cuentas un contador con cada animación quitada y usas anim.onremove para activar el contador.

Existen algunos métodos adicionales para llevar el control de la animación aún más:

  • animation.replaceState() proporciona un medio para hacer un seguimiento de si una animación está activa, persistente o quitada.
  • 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 configurar el modo compuesto de tus animaciones, lo que significa que pueden ser aditivas o acumulativas, además del modo predeterminado de "replace". Los modos compuestos permiten que los desarrolladores escriban animaciones distintas y tengan control sobre cómo se combinan los efectos. Ahora se admiten tres modos compuestos: 'replace' (el modo predeterminado), 'add' y 'accumulate'.

Cuando compones animaciones, un desarrollador puede escribir efectos cortos y diferentes, y verlos combinados. En el siguiente ejemplo, estamos aplicando un fotograma clave de rotación y escala a cada cuadro, y el único ajuste es el modo compuesto, que se agregó como opción:

Una demostración que muestra los modos compuestos predeterminado, de suma y de acumulación. Ver demostración de Glitch

En el modo compuesto predeterminado 'replace', la animación final reemplaza la propiedad de transformación y termina en rotate(360deg) scale(1.4). En el caso de 'add', el compuesto agrega la rotación y multiplica la escala, lo que da como resultado 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 particularidades de estos modos compuestos, consulta las enumeraciones de ComposeOperation y CombinedOperationOrAuto en la especificación de Web Animations.

Veamos un ejemplo de un elemento de la IU:

Un menú desplegable dinámico que tiene dos animaciones compuestas aplicadas. Ver demostración de Glitch

Aquí, se componen 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' });
  });

Próximos pasos para la API de Web Animations

Todas estas son nuevas incorporaciones interesantes a las funciones de animaciones de los navegadores actuales, y se sumarán muchas más en el futuro. Consulta estas futuras especificaciones para leer más sobre lo que viene a continuación: