Worklet de animación de Houdini

Potencia las animaciones de tu app web

Resumen: El Worklet de animación te permite escribir animaciones imperativas que se ejecutan a la velocidad de fotogramas nativa del dispositivo, para obtener Hacer que tus animaciones sean más resilientes ante los bloqueos del subproceso principal y que se puedan vincular para desplazarme en lugar de tiempo. El Worklet de animación está en Chrome Canary (detrás de "Funciones experimentales de la plataforma web" ) y estamos planificando una prueba de origen para Chrome 71. Puedes comenzar a usarlo como una mejora progresiva hoy.

¿Otra API de Animation?

En realidad, no, es una extensión de lo que ya tenemos, ¡y con buenos motivos! Empecemos por el principio. Si quieres animar cualquier elemento DOM en la Web hoy en día, tienes 2 opciones 1⁄2: Transiciones de CSS para transiciones simples de A a B, animaciones de CSS para animaciones basadas en el tiempo más complejas y potencialmente cíclicas, y la API de Web Animations (WAAPI) para animaciones complejas de forma casi arbitraria. La matriz de compatibilidad de WAAPI se ve bastante sombría, pero está en ascenso. Hasta entonces, hay una polyfill

Lo que tienen en común todos estos métodos es que son sin estado y se basa en el tiempo. Sin embargo, algunos de los efectos que prueban los desarrolladores no son o sin estado. Por ejemplo, el infame desplazador con paralaje es, como lo que implica, basado en el desplazamiento. Implementar un desplazador con paralaje de buen rendimiento en la Web hoy en día es sorprendentemente difícil.

¿Y qué sucede con la falta de estado? Piensa en la barra de direcciones de Chrome en Android, para ejemplo. Si te desplazas hacia abajo, este se hace fuera de la vista. Sin embargo, segundo cuando te desplaces hacia arriba, volverá, aunque estés a mitad de camino esa página. La animación depende no solo de la posición de desplazamiento, sino también de la dirección de desplazamiento anterior. Es con estado.

Otro problema es el diseño de las barras de desplazamiento. Son muy poco estilables, o bien al menos no lo suficientemente estilizado. ¿Qué sucede si quiero un gato nyan como barra de desplazamiento? Independientemente de la técnica que elijas, crear una barra de desplazamiento personalizada no es ni fácil.

El punto es que todas estas cosas son incómodas y difíciles o imposibles de implementar de manera eficiente. La mayoría de ellos dependen de eventos o requestAnimationFrame, que podría mantenerte en 60 FPS, incluso cuando la pantalla esté capaz de ejecutarse a 90 fps, 120 fps o más y usar una fracción de y preciado presupuesto de marco de hilo principal.

Animation Worklet amplía las capacidades de la pila de animaciones de la Web para que sea más fácil. este tipo de efectos. Antes de comenzar, asegurémonos de estar al tanto de los conceptos básicos de las animaciones.

Un manual básico sobre animaciones y cronogramas

WAAPI y Animation Worklet hacen un uso extensivo de las líneas de tiempo para permitirte para organizar las animaciones y los efectos como desees. Esta sección es una un repaso rápido o introducción a los cronogramas y cómo funcionan con las animaciones.

Cada documento tiene document.timeline. Comienza en 0 cuando el documento se que se creó y cuenta los milisegundos desde que el documento comenzó a existir. Todos las animaciones de un documento funcionan en relación con este cronograma.

Para ser un poco más concretos, veamos este fragmento de WAAPI

const animation = new Animation(
  new KeyframeEffect(
    document.querySelector('#a'),
    [
      {
        transform: 'translateX(0)',
      },
      {
        transform: 'translateX(500px)',
      },
      {
        transform: 'translateY(500px)',
      },
    ],
    {
      delay: 3000,
      duration: 2000,
      iterations: 3,
    }
  ),
  document.timeline
);

animation.play();

Cuando llamamos a animation.play(), la animación usa el elemento currentTime del cronograma. como su hora de inicio. Nuestra animación tiene un retraso de 3,000 ms, lo que significa que comenzará la animación (o se volverá "activa") cuando el cronograma alcance el "startTime".

  • 3,000. After that time, the animation engine will animate the given element from the first keyframe (translateX(0)), through all intermediate keyframes (translateX(500 px)) all the way to the last keyframe (translateY(500 px)) in exactly 2000ms, as prescribed by thedurationoptions. Since we have a duration of 2000ms, we will reach the middle keyframe when the timeline'scurrentTimeisstartTime + 3,000 + 1,000and the last keyframe atstartTime + 3,000 + 2,000`. La cuestión es que la la línea de tiempo controla en qué parte de la animación nos encontramos.

Cuando la animación haya llegado al último fotograma clave, regresará al primer fotograma clave e iniciar la siguiente iteración de la animación. Este proceso repite un un total de 3 veces desde que configuramos iterations: 3. Si quisiéramos que la animación nunca nos detuvimos, escribiríamos iterations: Number.POSITIVE_INFINITY. Este es el result del código arriba.

WAAPI es increíblemente potente y hay muchas más funciones en esta API, como flexibilización, desplazamientos de inicio, ponderaciones de fotogramas clave y el comportamiento de relleno que afectarían el rendimiento el alcance de este artículo. Si deseas obtener más información, te recomendamos leer este artículo sobre las animaciones de CSS en los trucos de CSS.

Cómo escribir un Worklet de animación

Ahora que ya comprendimos los plazos, podemos comenzar a analizar Worklet de animación y cómo te permite manipular las líneas de tiempo La animación La API de Worklet no solo se basa en WAAPI, sino que, en el sentido de la Web extensible, es una primitiva de nivel inferior que se explica cómo funciona WAAPI. En términos de sintaxis, son increíblemente similares:

Worklet de animación WAAPI
new WorkletAnimation(
  'passthrough',
  new KeyframeEffect(
    document.querySelector('#a'),
    [
      {
        transform: 'translateX(0)'
      },
      {
        transform: 'translateX(500px)'
      }
    ],
    {
      duration: 2000,
      iterations: Number.POSITIVE_INFINITY
    }
  ),
  document.timeline
).play();
      
        new Animation(

        new KeyframeEffect(
        document.querySelector('#a'),
        [
        {
        transform: 'translateX(0)'
        },
        {
        transform: 'translateX(500px)'
        }
        ],
        {
        duration: 2000,
        iterations: Number.POSITIVE_INFINITY
        }
        ),
        document.timeline
        ).play();
        

La diferencia está en el primer parámetro, que es el nombre del worklet. que genera esta animación.

Detección de funciones

Chrome es el primer navegador que incluye esta función, por lo que debes asegurarte de que tu código no solo espera que AnimationWorklet esté allí. Antes de cargar worklet, deberíamos detectar si el navegador del usuario es compatible con AnimationWorklet con una verificación simple:

if ('animationWorklet' in CSS) {
  // AnimationWorklet is supported!
}

Cómo cargar un worklet

Worklets es un concepto nuevo introducido por el grupo de trabajo de Houdini que las APIs nuevas sean más fáciles de compilar y escalar. Abordaremos los detalles de los worklets más adelante, pero por cuestiones de simplicidad, puedes considerarlas baratas y subprocesos ligeros (como trabajadores) por ahora.

Debemos asegurarnos de haber cargado un worklet con el nombre "passthrough", antes de declarar la animación:

// index.html
await CSS.animationWorklet.addModule('passthrough-aw.js');
// ... WorkletAnimation initialization from above ...

// passthrough-aw.js
registerAnimator(
  'passthrough',
  class {
    animate(currentTime, effect) {
      effect.localTime = currentTime;
    }
  }
);

¿Qué está pasando aquí? Registraremos una clase como animador usando el Llamada registerAnimator() de AnimationWorklet, que le da el nombre "passthrough" Es el mismo nombre que usamos en el constructor WorkletAnimation() anterior. Una vez que se completó el registro, la promesa devuelta por addModule() resolverá y podemos comenzar a crear animaciones con ese worklet.

Se llamará al método animate() de nuestra instancia para cada fotograma de la el navegador quiere renderizar, pasando el currentTime del cronograma de la animación así como el efecto que se está procesando actualmente. Solo tenemos una efecto, KeyframeEffect y usamos currentTime para establecer la localTime, por lo que este animador se llama "passthrough". Con este código para el worklet, la WAAPI y el AnimationWorklet anterior se comportan exactamente como mismo, como puedes ver en el demostración.

Hora

El parámetro currentTime de nuestro método animate() es el currentTime de la cronograma que pasamos al constructor WorkletAnimation(). En el anterior ejemplo, acabamos de pasar ese tiempo al efecto. Pero como se trata de código JavaScript, y podemos distorsionar el tiempo 💫

function remap(minIn, maxIn, minOut, maxOut, v) {
  return ((v - minIn) / (maxIn - minIn)) * (maxOut - minOut) + minOut;
}
registerAnimator(
  'sin',
  class {
    animate(currentTime, effect) {
      effect.localTime = remap(
        -1,
        1,
        0,
        2000,
        Math.sin((currentTime * 2 * Math.PI) / 2000)
      );
    }
  }
);

Estamos tomando el Math.sin() de currentTime y reasignando ese valor a el rango [0; 2000], que es el intervalo de tiempo para el que se define nuestro efecto. Ahora la animación se ve muy diferente, sin que cambiaron los fotogramas clave o las opciones de la animación. El código del worklet se puede arbitrariamente compleja y te permite definir de forma programática qué efectos se deben tocaron en qué orden y en qué medida.

Opciones sobre opciones

Se recomienda reutilizar un worklet y cambiar sus números. Por este motivo, el El constructor de WorkletAnimation te permite pasar un objeto de opciones al worklet:

registerAnimator(
  'factor',
  class {
    constructor(options = {}) {
      this.factor = options.factor || 1;
    }
    animate(currentTime, effect) {
      effect.localTime = currentTime * this.factor;
    }
  }
);

new WorkletAnimation(
  'factor',
  new KeyframeEffect(
    document.querySelector('#b'),
    [
      /* ... same keyframes as before ... */
    ],
    {
      duration: 2000,
      iterations: Number.POSITIVE_INFINITY,
    }
  ),
  document.timeline,
  {factor: 0.5}
).play();

En este ejemplo, Ambas animaciones se impulsan con el mismo código, pero con opciones diferentes.

Menciona tu estado local

Como ya dije antes, uno de los problemas clave que busca resolver el worklet de animación es animaciones con estado. Los worklets de animación pueden conservar el estado. Sin embargo, uno Una de las funciones principales de los worklets es que se pueden migrar a un o incluso destruirse para ahorrar recursos, lo cual para cada estado. Para evitar la pérdida de estado, el worklet de animación ofrece un hook que se llama antes de que se destruya un worklet, que puedes usar para devolver un estado. . Ese objeto se pasará al constructor cuando se transfiera el worklet se vuelven a crear. En la creación inicial, ese parámetro será undefined.

registerAnimator(
  'randomspin',
  class {
    constructor(options = {}, state = {}) {
      this.direction = state.direction || (Math.random() > 0.5 ? 1 : -1);
    }
    animate(currentTime, effect) {
      // Some math to make sure that `localTime` is always > 0.
      effect.localTime = 2000 + this.direction * (currentTime % 2000);
    }
    destroy() {
      return {
        direction: this.direction,
      };
    }
  }
);

Cada vez que actualices esta demostración, tendrás una tasa de 50/50 probabilidad en la dirección en que el cuadrado girará. Si el navegador eliminara el worklet y lo migres a un subproceso diferente, habrá otro Math.random() durante su creación, lo que podría provocar un cambio repentino de dirección IP. Para asegurarnos de que eso no suceda, devolvemos las animaciones elegida al azar como state y usarla en el constructor, si se proporciona.

Conexión con el continuo espacio-tiempo: ScrollTimeline

Como mostró la sección anterior, AnimationWorklet nos permite definir de forma programática cómo el avance del cronograma afecta los efectos animación. Sin embargo, hasta ahora, nuestro cronograma siempre ha sido document.timeline, registra el tiempo.

ScrollTimeline abre nuevas posibilidades y te permite generar animaciones. con desplazamiento en lugar de tiempo. Vamos a reutilizar nuestro primer "transferencia" worklet para esto demostración:

new WorkletAnimation(
  'passthrough',
  new KeyframeEffect(
    document.querySelector('#a'),
    [
      {
        transform: 'translateX(0)',
      },
      {
        transform: 'translateX(500px)',
      },
    ],
    {
      duration: 2000,
      fill: 'both',
    }
  ),
  new ScrollTimeline({
    scrollSource: document.querySelector('main'),
    orientation: 'vertical', // "horizontal" or "vertical".
    timeRange: 2000,
  })
).play();

En lugar de pasar document.timeline, crearemos un nuevo ScrollTimeline. Es posible que lo hayas adivinado: ScrollTimeline no usa tiempo, pero Posición de desplazamiento de scrollSource para establecer currentTime en el worklet. En proceso desplazado hasta la parte superior (o izquierda) significa currentTime = 0, mientras que que se desplaza hasta el final (o hacia la derecha), establece currentTime como timeRange Si te desplazas por el cuadro de esta demo, puedes controlar la posición del cuadro rojo.

Si creas un ScrollTimeline con un elemento que no se desplaza, el La currentTime del cronograma será NaN. En especial, con el diseño adaptable En mente, siempre debes estar preparado para NaN como tu currentTime. Es a menudo es razonable que el valor predeterminado sea 0.

Desde hace mucho tiempo se busca vincular animaciones con la posición de desplazamiento, pero nunca se logró en este nivel de fidelidad (además de los pasos soluciones alternativas con CSS3D). El Worklet de animación permite que estos efectos de forma sencilla y con un alto rendimiento. Por ejemplo: un efecto de desplazamiento con paralaje como este demo muestra que ahora solo toma algunas líneas para definir una animación basada en desplazamientos.

Detrás de escena

Worklets

Los Worklets son contextos de JavaScript con un alcance aislado y una API muy pequeña. de ataque de la nube. La superficie de API pequeña permite una optimización más agresiva desde la navegador, especialmente en dispositivos de gama baja. Además, los worklets no están vinculados a un bucle de eventos específico, pero que se puede mover entre subprocesos según sea necesario. Este es especialmente importante para AnimationWorklet.

Compositor NSync

Quizás sepas que algunas propiedades de CSS son rápidas de animar, mientras que otras son no. Algunas propiedades solo necesitan trabajo en la GPU para ser animadas, mientras que otras obligar al navegador a rediseñar todo el documento

En Chrome (como en muchos otros navegadores) tenemos un proceso llamado compositor, cuyo trabajo es, y estoy simplificando mucho, organizar capas y texturas y usa la GPU para actualizar la pantalla con la mayor frecuencia posible idealmente tan rápido como se pueda actualizar la pantalla (por lo general, 60 Hz). Según cuál sea Se están animando las propiedades de CSS, es posible que el navegador solo necesite el compositor hace su trabajo, mientras que otras propiedades deben ejecutar el diseño, que es un que solo puede hacer el subproceso principal. Según las propiedades que planeas animar, tu worklet de animación estará vinculado al subproceso o se ejecuta en un subproceso separado sincronizado con el compositor.

Dar una palmada en la muñeca

Por lo general, hay solo un proceso del compositor que posiblemente se comparta entre en varias pestañas, ya que la GPU es un recurso muy complejo. Si el compositor obtiene el navegador se detiene y deja de responder la entrada del usuario. Esto debe evitarse a toda costa. Entonces, ¿qué sucede si el worklet no puede entregar los datos que el compositor necesita a tiempo para que la trama se se renderiza?

Si esto sucede, el worklet está permitido, según la especificación, para "deslizar". Se queda atrás el compositor, y este puede reutilizar los datos de la última trama para mantener alta la velocidad de fotogramas. Visualmente, se verá como bloqueo, pero la diferencia es que el navegador sigue respondiendo a las entradas del usuario.

Conclusión

AnimationWorklet tiene muchas facetas y los beneficios que aporta a la Web. Los beneficios obvios son más control sobre las animaciones y nuevas formas de generar animaciones para aportar un nuevo nivel de fidelidad visual a la Web. Pero las APIs también te permite hacer que tu app sea más resistente a los bloqueos acceso a todos los beneficios nuevos al mismo tiempo.

Animation Worklet está disponible en Canary y queremos realizar una prueba de origen con Chrome 71. Esperamos con ansias tus nuevas experiencias en la Web y que escuches sobre lo que podemos mejorar. También hay un polyfill que te proporciona la misma API, pero no el aislamiento de rendimiento.

Ten en cuenta que las transiciones y las animaciones de CSS siguen siendo válidas y puede ser mucho más simple para las animaciones básicas. Pero si necesitas saber elegante, AnimationWorklet te ayudará.