Animaciones vinculadas a desplazamiento con la API de animaciones web (WAAPI) y ScrollTimeline | Programar Plus

La especificación de animaciones vinculadas al desplazamiento es una adición próxima y experimental que nos permite vincular el progreso de la animación con el progreso del desplazamiento: a medida que se desplaza hacia arriba y hacia abajo en un contenedor de desplazamiento, una animación vinculada también avanza o retrocede en consecuencia.

Cubrimos algunos casos de uso en una pieza anterior aquí sobre CSS-Tricks, todos impulsados ​​por CSS @scroll-timeline en regla y animation-timeline propiedad que proporciona la especificación; sí, eso es correcto: todos esos casos de uso se crearon utilizando solo HTML y CSS. Sin JavaScript.

Además de la interfaz CSS que obtenemos con la especificación de animaciones vinculadas a desplazamiento, también describe una interfaz JavaScript para implementar animaciones vinculadas a desplazamiento. Echemos un vistazo a ScrollTimeline class y cómo usarlo con la API de animaciones web.

API de animaciones web: un resumen rápido

La API de animaciones web (WAAPI) se ha cubierto aquí en Programar Plusantes. Como pequeño resumen, la API nos permite construir animaciones y controlar su reproducción con JavaScript.

Tome la siguiente animación CSS, por ejemplo, donde una barra se encuentra en la parte superior de la página y:

  1. anima de red a darkred, entonces
  2. anima de ancho cero a ancho completo (escalando el eje x).

Al traducir la animación CSS a su contraparte WAAPI, el código se convierte en esto:

new Animation(
  new KeyframeEffect(
    document.querySelector('.progressbar'),
    {
      backgroundColor: ['red', 'darkred'],
      transform: ['scaleX(0)', 'scaleX(1)'],
    },
    {
      duration: 2500,
      fill: 'forwards',
      easing: 'linear',
    }
  )
).play();

O alternativamente, usando una sintaxis más corta con Element.animate():

document.querySelector('.progressbar').animate(
  {
    backgroundColor: ['red', 'darkred'],
    transform: ['scaleX(0)', 'scaleX(1)'],
  },
  {
    duration: 2500,
    fill: 'forwards',
    easing: 'linear',
   }
);

En esos dos últimos ejemplos de JavaScript, podemos distinguir dos cosas. Primero un keyframes objeto que describe qué propiedades animar:

{
  backgroundColor: ['red', 'darkred'],
  transform: ['scaleX(0)', 'scaleX(1)'],
}

El segundo es un options Objeto que configura la duración de la animación, easing, etc .:

{
  duration: 2500,
  fill: 'forwards',
  easing: 'linear',
}

Crear y adjuntar una línea de tiempo de desplazamiento

Para que nuestra animación sea impulsada por desplazamiento, en lugar del tic monótono de un reloj, podemos mantener nuestro código WAAPI existente, pero necesitamos extenderlo adjuntando un ScrollTimeline instancia a ella.

Esta ScrollTimeline clase nos permite describir un AnimationTimeline cuyos valores de tiempo no están determinados por la hora del reloj de pared, sino por el progreso del desplazamiento en un contenedor de desplazamiento. Se puede configurar con algunas opciones:

  • source: El elemento desplazable cuyo desplazamiento desencadena la activación e impulsa el progreso de la línea de tiempo. Por defecto, esto es document.scrollingElement (es decir, el contenedor de desplazamiento que desplaza todo el documento).
  • orientation: Determina la dirección del desplazamiento, que desencadena la activación e impulsa el progreso de la línea de tiempo. Por defecto, esto es vertical (o block como valor lógico).
  • scrollOffsets: Estos determinan las compensaciones de desplazamiento efectivas, moviéndose en la dirección especificada por el orientation valor. Constituyen intervalos de progreso igualmente distanciados en los que la línea de tiempo está activa.

Estas opciones se pasan al constructor. Por ejemplo:

const myScrollTimeline = new ScrollTimeline({
  source: document.scrollingElement,
  orientation: 'block',
  scrollOffsets: [
    new CSSUnitValue(0, 'percent'),
    new CSSUnitValue(100, 'percent'),
  ],
});

No es una coincidencia que estas opciones sean exactamente las mismas que las del CSS @scroll-timeline descriptores. Ambos enfoques le permiten lograr el mismo resultado, con la única diferencia del lenguaje que utiliza para definirlos.

Para adjuntar nuestro recién creado ScrollTimeline instancia a una animación, lo pasamos como el segundo argumento en el Animation constructor:

new Animation(
  new KeyframeEffect(
    document.querySelector('#progress'),
    { transform: ['scaleX(0)', 'scaleX(1)'], },
    { duration: 1, fill: 'forwards' }
  ),
  myScrollTimeline
).play();

Al usar el Element.animate() sintaxis, configúrelo como timeline opción en el options objeto:

document.querySelector("#progress").animate(
  {
    transform: ["scaleX(0)", "scaleX(1)"]
  },
  { 
    duration: 1, 
    fill: "forwards", 
    timeline: myScrollTimeline
  }
);

Con este código en su lugar, la animación es impulsada por nuestro ScrollTimeline instancia en lugar de la predeterminada DocumentTimeline.

La implementación experimental actual en Chromium usa scrollSource en vez de source. Esa es la razón por la que ves a ambos source y scrollSource en los ejemplos de código.

Unas palabras sobre la compatibilidad del navegador

En el momento de redactar este artículo, solo los navegadores Chromium admiten la ScrollTimeline clase, detrás de una bandera de función. Afortunadamente, existe el Polyfill Scroll-Timeline de Robert Flack que podemos usar para llenar los vacíos no compatibles en todos los demás navegadores. De hecho, todas las demostraciones incluidas en este artículo lo incluyen.

El polyfill está disponible como módulo y se registra si no se detecta soporte. Para incluirlo, agregue lo siguiente import declaración a su código JavaScript:

import 'https://flackr.github.io/scroll-timeline/dist/scroll-timeline.js';

El polyfill también registra las clases requeridas del modelo de objetos con tipo CSS, en caso de que el navegador no lo admita. (👀 Mirándote, Safari).

Líneas de tiempo de desplazamiento avanzadas

Además de las compensaciones absolutas, las animaciones con enlaces de desplazamiento también pueden funcionar con compensaciones basadas en elementos:

Con este tipo de desplazamiento de desplazamiento, la animación se basa en la ubicación de un elemento dentro del contenedor de desplazamiento.

Normalmente, esto se utiliza para animar un elemento a medida que entra en el puerto de desplazamiento hasta que sale del puerto de desplazamiento; por ejemplo, mientras se cruza.

Un desplazamiento basado en elementos consta de tres partes que lo describen:

  1. target: El elemento DOM rastreado.
  2. edge: Esto es lo que ScrollTimeline‘s source relojes para el target para cruzar.
  3. threshold: Un número que va desde 0.0 a 1.0 que indica la cantidad de target es visible en el puerto de desplazamiento en el edge. (Es posible que sepas esto por IntersectionObserver.)

Aquí hay una visualización:

Si desea obtener más información sobre las compensaciones basadas en elementos, incluido cómo funcionan y ejemplos de compensaciones de uso común, consulte este artículo.

Las compensaciones basadas en elementos también son compatibles con JS ScrollTimeline interfaz. Para definir uno, use un objeto regular:

{
  target: document.querySelector('#targetEl'),
  edge: 'end',
  threshold: 0.5,
}

Normalmente, pasa dos de estos objetos a la scrollOffsets propiedad.

const $image = document.querySelector('#myImage');

$image.animate(
  {
    opacity: [0, 1],
    clipPath: ['inset(45% 20% 45% 20%)', 'inset(0% 0% 0% 0%)'],
  },
  {
    duration: 1,
    fill: "both",
    timeline: new ScrollTimeline({
      scrollSource: document.scrollingElement,
      timeRange: 1,
      fill: "both",
      scrollOffsets: [
        { target: $image, edge: 'end', threshold: 0.5 },
        { target: $image, edge: 'end', threshold: 1 },
      ],
    }),
  }
);

Este código se utiliza en la siguiente demostración a continuación. Es una nueva versión de JavaScript del efecto que cubrí la última vez: cuando una imagen se desplaza hacia la ventana gráfica, se desvanece y se desenmascara.

Más ejemplos

Aquí hay algunos ejemplos más que cociné.

Sección de desplazamiento horizontal

Esto se basa en una demostración de Cameron Knight, que presenta una sección de desplazamiento horizontal. Se comporta de manera similar, pero usa ScrollTimeline en lugar de GSAP ScrollTrigger.

Para obtener más información sobre cómo funciona este código y ver una versión pura de CSS, consulte este artículo.

Flujo de corriente

¿Recuerdas CoverFlow de iTunes? Bueno, aquí tienes una versión construida con ScrollTimeline:

Esta demostración no se comporta al 100% como se esperaba en Chromium debido a un error. El problema es que las posiciones inicial y final se calculan incorrectamente. Puede encontrar una explicación (con videos) en este hilo de Twitter.

Puede encontrar más información sobre esta demostración en este artículo.

CSS o JavaScript?

No hay una diferencia real al usar CSS o JavaScript para las animaciones vinculadas a desplazamiento, excepto por el lenguaje utilizado: ambos usan los mismos conceptos y construcciones. En el verdadero espíritu de la mejora progresiva, me aferraría a CSS para este tipo de efectos.

Sin embargo, como cubrimos anteriormente, el soporte para la implementación basada en CSS es bastante pobre en el momento de escribir este artículo:

  • Chromium lo admite detrás de una bandera de función.
  • Firefox está preparando algunos trabajos para ello. (Boleto de Mozilla # 1676780)
  • Aún no hay noticias de Safari. (Boleto de WebKit # 222295)

Debido a ese soporte deficiente, ciertamente llegará más lejos con JavaScript en este mismo momento. Solo asegúrese de que su sitio también se pueda ver y consumir cuando JavaScript esté deshabilitado. 😉