Worklet d'animation de Houdini

Optimiser les animations de votre application Web

Résumé:le workflow d'animation vous permet d'écrire des animations impératives qui s'exécutent à la fréquence d'images native de l'appareil, pour une fluidité sans à-coups supplémentaireTM. rendre vos animations plus résistantes aux à-coups du thread principal et être liées ; de faire défiler la page au lieu du temps. Le Worklet d'animation est disponible dans Chrome Canary (derrière la "Fonctionnalités expérimentales de la plate-forme Web" ) et nous prévoyons une phase d'évaluation pour Chrome 71. Vous pouvez commencer à l'utiliser comme une amélioration progressive aujourd'hui.

Une autre API d'animation ?

En fait, non, c'est une extension de ce que nous avons déjà, et à juste titre ! Commençons par le début. Pour animer n'importe quel élément DOM sur le Web Aujourd'hui, vous avez 2,5 choix possibles: Transitions CSS pour des transitions simples de A à B, les animations CSS pour Des animations potentiellement cycliques et plus complexes basées sur le temps ainsi que l'API Web Animations (WAAPI) pour les animations complexes presque arbitrairement. La matrice de support de WAAPI est plutôt morose, mais il progresse. D'ici là, il existe un polyfill

Ce que toutes ces méthodes ont en commun, c'est qu'elles sont sans état et en fonction du temps. Mais certains des effets qu'essaient les développeurs ne sont ni basées sur le temps ni sans état. Par exemple, le célèbre outil de défilement parallaxe que son nom implique : piloté par défilement. Il est étonnamment difficile d'implémenter un conteneur de défilement parallaxe performant sur le Web.

Et qu'en est-il de la notion d'état sans état ? Pensez à la barre d'adresse de Chrome sur Android, pour à titre d'exemple. Si vous faites défiler l'écran vers le bas, elle disparaîtra du champ de vision. Toutefois, lorsque vous faites défiler la page vers le haut, elle s'affiche à nouveau, même si vous êtes à mi-chemin vers le bas de cette page. L'animation dépend non seulement de la position de défilement, mais aussi la direction de défilement précédente. Elle est avec état.

Un autre problème concerne le style des barres de défilement. Ils sont notoirement peu stylisés. mais pas assez stylisés. Comment faire si je veux un chat nyan comme barre de défilement ? Quelle que soit la technique que vous choisissez, la création d'une barre de défilement personnalisée n'est ni performante ni facile.

Le fait est que toutes ces choses sont gênantes et difficiles, voire impossibles à une mise en œuvre efficace. La plupart d'entre elles s'appuient sur des événements et/ou requestAnimationFrame, ce qui peut vous maintenir à 60 FPS, même lorsque l'écran est allumé capable de fonctionner à 90, 120 images par seconde ou plus, et qui utilise très peu précieux budget des frames du thread principal.

Le Worklet d'animation étend les capacités de la pile d'animations du Web pour rendre ce type d'effets. Avant d'entrer dans le vif du sujet, assurez-vous d'avoir à jour sur les bases des animations.

Présentation des animations et des timelines

WAAPI et le workflow d'animation utilisent de manière intensive les timelines pour vous permettre d'orchestrer les animations et les effets comme vous le souhaitez. Cette section un rappel rapide ou une introduction aux chronologies et à leur fonctionnement avec les animations.

Chaque document contient document.timeline. Il commence à 0 lorsque le document est créée et compte les millisecondes écoulées depuis le début de la création du document. Tous les animations d'un document fonctionnent par rapport à cette timeline.

Pour rendre les choses un peu plus concrètes, jetons un coup d’œil à cet extrait 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();

Lorsque nous appelons animation.play(), l'animation utilise le currentTime de la timeline comme heure de début. Notre animation a un retard de 3 000 ms, ce qui signifie que l'animation démarre (ou devient "active") lorsque la timeline atteint "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(500px)) all the way to the last keyframe (translateY(500px)) in exactly 2000ms, as prescribed by theduréeoptions. Since we have a duration of 2000ms, we will reach the middle keyframe when the timeline'sheure actuelleisheure de début + 3 000 + 1 000and the last keyframe atheure de début + 3 000 + 2 000`. Le fait est que les permet de contrôler l'endroit où nous nous trouvons dans l'animation.

Lorsque l'animation atteint la dernière image clé, elle revient à la première et lancer l'itération suivante de l'animation. Ce processus répète une trois fois au total depuis que nous avons défini iterations: 3. Si nous voulons que l'animation sans jamais vous arrêter, nous écrivons iterations: Number.POSITIVE_INFINITY. Voici les result du code ci-dessus.

L'API WAAPI est incroyablement puissante et offre beaucoup plus de fonctionnalités comme le lissage de vitesse, les décalages de début, les pondérations d'image clé et le comportement de remplissage qui entraîneraient une de cet article. Pour en savoir plus, nous vous invitons à lire cet article sur les animations CSS et les astuces CSS.

Écrire un workflow d'animation

Maintenant que nous avons abordé le concept de chronologie, Worklet d'animation et comment elle vous permet de manipuler les timelines. L'Animation L'API Worklet est non seulement basée sur WAAPI, mais constitue aussi, au sens du Web extensible, une primitive de niveau inférieur qui explique le fonctionnement de WAAPI. En termes de syntaxe, ils sont incroyablement similaires:

Worklet d'animation API WAP
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 différence réside dans le premier paramètre, qui est le nom du worklet. qui génère cette animation.

Détection de caractéristiques

Chrome est le premier navigateur à intégrer cette fonctionnalité. Vous devez donc vous assurer que votre le code ne s'attend pas seulement à ce que AnimationWorklet soit présent. Avant de charger Worklet, nous devons détecter si le navigateur de l'utilisateur prend en charge AnimationWorklet par une simple vérification:

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

Charger un Worklet

Les Worklets sont un nouveau concept introduit par le groupe de travail Houdini pour rendre bon nombre des les nouvelles API plus faciles à créer et à faire évoluer. Nous aborderons en détail les Worklets un peu plus tard, mais pour plus de simplicité, vous pouvez les considérer comme bon marché et des threads légers (comme les nœuds de calcul) pour le moment.

Nous devons nous assurer que nous avons chargé un worklet nommé « passthrough », avant de déclarer l'animation:

// 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;
    }
  }
);

Que se passe-t-il ici ? Nous enregistrons une classe en tant qu'animateur à l'aide de la méthode l'appel registerAnimator() d'AnimationWorklet, qui lui est attribué par le nom "passthrough". Il s'agit du nom que nous avons utilisé dans le constructeur WorkletAnimation() ci-dessus. Une fois que l'enregistrement est terminé, la promesse renvoyée par addModule() sera résolue et nous pouvons commencer à créer des animations à l'aide de ce Worklet.

La méthode animate() de notre instance sera appelée pour chaque frame le navigateur souhaite effectuer le rendu, en transmettant le currentTime de la timeline de l'animation ainsi que l'effet en cours de traitement. Nous n'en avons qu'un l'effet KeyframeEffect, et nous utilisons currentTime pour définir le localTime, c'est pourquoi cet animateur est appelé "passthrough". Avec ce code pour le Worklet, WAAPI et l'AnimationWorklet ci-dessus se comportent exactement comme vous pouvez le voir demo.

Heure

Le paramètre currentTime de la méthode animate() est l'currentTime de de la timeline que nous avons transmise au constructeur WorkletAnimation(). Lors de la précédente dans cet exemple, nous avons juste fait passer ce temps Mais comme il s’agit du code JavaScript, et nous pouvons déformer le temps 💫

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)
      );
    }
  }
);

Nous prenons le Math.sin() de currentTime et nous remappons cette valeur à la plage [0; 2000], qui est la période pour laquelle l'effet est défini. Maintenant l'animation est très différente, sans que les utilisateurs modifié les images clés ou les options de l'animation. Le code du Worklet peut être arbitrairement complexe, et vous permet de définir de manière programmatique les effets dans quel ordre et dans quelle mesure.

Options plutôt que "Options"

Vous voudrez peut-être réutiliser un Worklet et modifier ses numéros. C'est pourquoi Le constructeur WorkletAnimation vous permet de transmettre un objet options au 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();

Dans cet exemple, les deux animations sont régies par le même code, mais avec des options différentes.

Affichez le département de votre région !

Comme je l'ai indiqué précédemment, l'un des principaux problèmes du Worklet d'animation à résoudre est les animations avec état. Les Worklets d'animation sont autorisés à conserver un état. Toutefois, une fonctionnalités principales des Worklets est qu'ils peuvent être migrés vers une autre ou être détruits pour préserver les ressources, ce qui détruirait aussi de l'état. Pour éviter de perdre un état, le Worklet d'animation propose un hook qui est appelé avant qu'un Worklet ne soit détruit. Vous pouvez l'utiliser pour renvoyer un état . Cet objet est transmis au constructeur lorsque le Worklet est recréés. Lors de la création initiale, ce paramètre sera 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,
      };
    }
  }
);

Chaque fois que vous actualisez cette démonstration, vous obtenez chance dans quelle direction le carré tournera. Si le navigateur venait à tomber en panne le Worklet et le migrer vers un autre thread, il y aurait une autre l'appel Math.random() lors de la création, ce qui peut entraîner un changement soudain de dans la direction souhaitée. Pour éviter que cela ne se produise, nous renvoyons les animations la direction choisie de manière aléatoire en tant qu'state et de l'utiliser dans le constructeur, le cas échéant.

S'intégrer au continuum espace-temps: ScrollTimeline

Comme l'a montré la section précédente, AnimationWorklet nous permet de de définir de manière programmatique la manière dont l'avance de la chronologie affecte les effets de l'animation. Mais jusqu'à présent, notre calendrier a toujours été de document.timeline, ce qui effectue un suivi du temps.

ScrollTimeline ouvre de nouvelles possibilités et vous permet de générer des animations en faisant défiler l'écran au lieu du temps. Nous allons réutiliser notre tout premier "passthrough" (passthrough) Worklet pour ceci demo:

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();

Au lieu de transmettre document.timeline, nous créons un ScrollTimeline. Vous l'avez peut-être deviné, ScrollTimeline n'utilise pas le temps, mais les Position de défilement de scrollSource pour définir currentTime dans le Worklet. Être jusqu'en haut (ou à gauche) signifie currentTime = 0, tandis que jusqu'en bas (ou à droite) définit currentTime sur timeRange Si vous faites défiler la boîte demo, vous pouvez contrôler la position du cadre rouge.

Si vous créez un ScrollTimeline avec un élément qui ne fait pas défiler l'écran, la valeur currentTime de l'historique sera NaN. Donc, en particulier avec la conception réactive, Vous devez toujours être prêt à utiliser NaN comme currentTime. Il est souvent que la valeur par défaut est 0.

Lier des animations à la position de défilement est un point commun depuis longtemps. mais n'a jamais été vraiment atteint à ce niveau de fidélité (à part les attaques solutions avec CSS3D). Le Worklet d'animation permet à ces effets d'être implémentées de manière simple tout en étant extrêmement performantes. Par exemple: un effet de défilement parallaxe comme celui-ci demo montre que Il suffit désormais de quelques lignes pour définir une animation liée au défilement.

dans le détail

Worklets

Les Worklets sont des contextes JavaScript avec un champ d'application isolé et une API très petite sur la surface de l'écran. La petite surface de l'API permet une optimisation plus agressive à partir de en particulier sur les appareils bas de gamme. De plus, les Worklets ne sont pas liés une boucle d'événements spécifique, mais ils peuvent être déplacés d'un thread à un autre si nécessaire. C'est particulièrement important pour AnimationWorklet.

Compositor NSync

Vous savez peut-être que certaines propriétés CSS s'animent rapidement, non. Certaines propriétés nécessitent juste un peu de travail sur le GPU pour être animées, tandis que d'autres Forcer le navigateur à remettre en page l'ensemble du document

Dans Chrome (comme dans de nombreux autres navigateurs), nous avons un processus appelé le compositeur, dont le travail consiste à organiser les couches les textures, puis utiliser le GPU pour mettre à jour l'écran aussi régulièrement que possible, idéalement aussi vite que l'écran peut se mettre à jour (généralement 60 Hz). En fonction du les propriétés CSS sont animées, le navigateur aura peut-être juste besoin du le compositeur s'en charge, tandis que d'autres propriétés doivent exécuter la mise en page, que seul le thread principal peut effectuer. En fonction des propriétés que vous que vous prévoyez d'animer, votre workflow d'animation sera soit lié au ou l'exécuter dans un thread distinct synchronisé avec le compositeur.

Frappe au poignet

Il n'existe généralement qu'un seul processus de compositeur pouvant être partagé plusieurs onglets, car le GPU est une ressource hautement conflictuelle. Si le compositeur reçoit bloqué, le navigateur s'interrompt et ne répond plus les entrées utilisateur. Cela doit être évité à tout prix. Que se passe-t-il si votre le Worklet ne peut pas fournir les données dont le compositeur a besoin à temps pour que la trame soit s'affiche-t-il ?

Dans ce cas, le Worklet est autorisé (conformément à la spécification) à "glisser". Elle prend du retard le compositeur est autorisé à réutiliser les données de la dernière trame pour maintenir la fréquence d'images. Visuellement, cela ressemble à un à-coup, mais la différence est que le navigateur répond toujours à l'entrée utilisateur.

Conclusion

AnimationWorklet présente de nombreux facettes et les avantages qu'elle apporte au Web. L'avantage est évident : plus de contrôle sur les animations et de nouvelles façons des animations pour apporter un nouveau niveau de fidélité visuelle sur le Web. Toutefois, les API vous permet également de la rendre plus résistante aux à-coups l'accès à tous ces avantages en même temps.

Le Worklet d'animation est en version Canary et nous souhaitons lancer une phase d'évaluation avec Chrome 71. Nous avons hâte de découvrir vos nouvelles expériences Web et de recevoir sur ce que nous pouvons améliorer. Un polyfill qui vous donne la même API, mais ne fournit pas l'isolation des performances.

N'oubliez pas que les transitions et les animations CSS restent valides. et peut être beaucoup plus simple pour les animations de base. Mais si vous devez aller AnimationWorklet est là pour vous !