Cette page a été traduite à partir de l'anglais par la communauté. Vous pouvez contribuer en rejoignant la communauté francophone sur MDN Web Docs.

View in English Always switch to English

Utilisation de l'API Web Animations

L'API Web Animations nous permet de construire des animations et de contrôler leur lecture avec JavaScript. Cet article vous mettra sur la bonne voie avec des démonstrations et des tutoriels inspirés d'« Alice au pays des merveilles ».

Découvrir l'API Web Animations

L'API Web Animations ouvre le moteur d'animation du navigateur aux développeur·euse·s et à la manipulation par JavaScript. Cette API a été conçue pour sous-tendre les implémentations d'Animations CSS et de Transitions CSS, et laisse la porte ouverte à de futurs effets d'animation. C'est l'une des façons les plus performantes d'animer sur le Web, laissant le navigateur effectuer ses propres optimisations internes sans détournements, coercition, ni Window.requestAnimationFrame().

Avec l'API Web Animations, nous pouvons déplacer des animations interactives des feuilles de style vers JavaScript, séparant ainsi la présentation du comportement. Nous n'avons plus besoin de recourir à des techniques lourdes pour le DOM comme l'écriture de propriétés CSS et l'application de classes aux éléments pour contrôler le sens de lecture. Et contrairement au CSS purement déclaratif, JavaScript nous permet aussi de définir dynamiquement des valeurs, des propriétés jusqu'aux durées. Pour créer des bibliothèques d'animation personnalisées et des animations interactives, l'API Web Animations peut être l'outil idoine. Voyons ce qu'elle permet.

Cette page contient une suite d'exemples utilisant l'API Web Animations, inspirés d'Alice au pays des merveilles. Ces exemples ont été créés et partagés gracieusement par Rachel Nabors (angl.). L'ensemble complet d'exemples (angl.) est disponible sur CodePen ; ici, nous présentons ceux qui sont pertinents pour notre documentation.

Écrire des animations CSS avec l'API Web Animations

Une façon familière d'aborder l'API Web Animations consiste à partir d'un sujet que la plupart des développeur·euse·s Web ont déjà expérimenté : les animations CSS. Les animations CSS ont une syntaxe familière qui se prête bien à la démonstration.

La version CSS

Voici une animation de « tumbling » (ou acrobatie en français) écrite en CSS montrant Alice tombant dans le terrier qui mène au pays des merveilles :

Remarquez que l'arrière-plan se déplace, Alice tourne et sa couleur change avec un décalage par rapport à sa rotation. Nous allons nous concentrer uniquement sur Alice pour ce tutoriel. Vous pouvez consulter le code source complet en cliquant sur « Play » dans le bloc de code. Voici le CSS simplifié qui contrôle l'animation d'Alice :

css
#alice {
  animation: alice-tumbling infinite 3s linear;
}

@keyframes alice-tumbling {
  0% {
    color: black;
    transform: rotate(0) translate3d(-50%, -50%, 0);
  }
  30% {
    color: #431236;
  }
  100% {
    color: black;
    transform: rotate(360deg) translate3d(-50%, -50%, 0);
  }
}

Cela change la couleur d'Alice et la rotation de sa transformation sur 3 secondes à un rythme constant (linear) et boucle indéfiniment. Dans le bloc @keyframes, on voit qu'à 30 % de chaque boucle (environ 0,9s), la couleur d'Alice passe du noir à un bordeaux profond, puis revient au noir à la fin de la boucle.

Transposer en JavaScript

Essayons maintenant de créer la même animation avec l'API Web Animations.

Représenter les images-clés

La première chose à faire est de créer un objet Keyframe correspondant à notre bloc @keyframes en CSS :

js
const aliceTumbling = [
  { transform: "rotate(0) translate3d(-50%, -50%, 0)", color: "black" },
  { color: "#431236", offset: 0.3 },
  { transform: "rotate(360deg) translate3d(-50%, -50%, 0)", color: "black" },
];

Ici, nous utilisons un tableau contenant plusieurs objets. Chaque objet représente une clé de l'animation CSS d'origine. Cependant, contrairement au CSS, l'API Web Animations n'a pas besoin qu'on lui indique explicitement les pourcentages le long de l'animation auxquels chaque clé doit apparaître. Elle divisera automatiquement l'animation en parts égales en fonction du nombre de clés fourni. Cela signifie qu'un objet d'images-clés à trois entrées jouera la clé du milieu à 50 % de chaque boucle, sauf indication contraire.

Quand on veut fixer explicitement le décalage d'une clé par rapport aux autres, on peut spécifier un offset directement dans l'objet, séparé de la déclaration par une virgule. Dans l'exemple ci-dessus, pour s'assurer que la couleur d'Alice change à 30 % (et non 50 %), on lui donne offset: 0.3.

Actuellement, il faut fournir au moins deux images-clés (représentant les états de début et de fin de la séquence). Si la liste ne contient qu'une seule entrée, Element.animate() peut lever une exception DOMException de type NotSupportedError dans certains navigateurs, en attendant leur mise à jour.

En résumé : les clés sont espacées uniformément par défaut, sauf si vous définissez un offset sur une clé. Pratique.

Représenter les propriétés de minutage

Nous devons aussi créer un objet de propriétés de minutage correspondant aux valeurs de l'animation d'Alice :

js
const aliceTiming = {
  duration: 3000,
  iterations: Infinity,
};

On remarque quelques différences avec la représentation CSS :

  • La durée est en millisecondes plutôt qu'en secondes — 3000 et non 3s. Comme setTimeout() et Window.requestAnimationFrame(), l'API Web Animations ne prend que des millisecondes.
  • On utilise iterations et non iteration-count.

Note : Il existe de petites différences de terminologie entre les animations CSS et les animations Web. Par exemple, les Web Animations n'utilisent pas la chaîne 'infinite', mais le mot-clé JavaScript Infinity. Et au lieu de timing-function, on utilise easing. Nous ne listons pas de valeur easing ici car, contrairement aux animations CSS où la valeur par défaut d'animation-timing-function est ease, l'API Web Animations utilise par défaut linear — ce qui convient ici.

Assembler les pièces

Il est temps d'assembler le tout avec la méthode Element.animate() :

js
document.getElementById("alice").animate(aliceTumbling, aliceTiming);

Et voilà : l'animation démarre :

La méthode animate() peut être appelée sur n'importe quel élément du DOM qui peut être animé avec CSS. Elle peut s'écrire de plusieurs façons. Au lieu de créer des objets séparés pour les images-clés et le minutage, on peut passer leurs valeurs directement, ainsi :

js
document.getElementById("alice").animate(
  [
    { transform: "rotate(0) translate3d(-50%, -50%, 0)", color: "black" },
    { color: "#431236", offset: 0.3 },
    { transform: "rotate(360deg) translate3d(-50%, -50%, 0)", color: "black" },
  ],
  {
    duration: 3000,
    iterations: Infinity,
  },
);

Mieux encore, si l'on ne veut préciser que la durée de l'animation, sans ses itérations (par défaut, une animation s'exécute une fois), on peut passer uniquement la durée en millisecondes :

js
document.getElementById("alice").animate(
  [
    { transform: "rotate(0) translate3d(-50%, -50%, 0)", color: "black" },
    { color: "#431236", offset: 0.3 },
    { transform: "rotate(360deg) translate3d(-50%, -50%, 0)", color: "black" },
  ],
  3000,
);

Contrôler la lecture avec play(), pause(), reverse() et updatePlaybackRate()

On peut écrire des animations CSS avec l'API Web Animations, mais là où l'API est vraiment utile, c'est pour manipuler la lecture. L'API offre plusieurs méthodes pour contrôler la lecture. Observons la mise en pause et la lecture dans l'exemple « Suis le lapin blanc » :

Dans cet exemple, le lapin blanc a une animation qui le fait descendre dans un terrier. Elle n'est déclenchée que lorsque l'utilisateur·ice clique dessus.

Mettre en pause et lire des animations

Nous pouvons animer le lapin avec animate() comme d'habitude :

js

La méthode Element.animate() démarre immédiatement après son appel. Pour éviter que le gâteau ne se « mange » tout seul avant que la personne n'ait le temps de cliquer, on appelle Animation.pause() juste après sa définition :

js

Note : Alternativement, on peut définir rabbitDownAnimation avec le constructeur Animation(), qui ne commence pas à jouer avant l'appel à play().

Nous pouvons ensuite utiliser Animation.play() pour lancer l'animation quand on le souhaite. Plus précisément, nous voulons la lier à une action de clic. On y parvient ainsi :

js

Quand une personne clique ou touche le lapin, nous pouvons appeler downHeGoes pour faire jouer toutes les animations.

Autres méthodes utiles

Outre la mise en pause et la lecture, on peut utiliser les méthodes suivantes sur une animation :

Regardons d'abord playbackRate : une valeur négative fait jouer une animation à l'envers. Dans De l'autre côté du miroir, Alice se rend dans un monde où elle doit courir pour rester sur place — et courir deux fois plus vite pour avancer. Dans l'exemple de la course de la Reine Rouge, Alice et la Reine Rouge courent pour rester sur place :

Comme les jeunes enfants se fatiguent vite, contrairement aux pièces d'échecs automates, Alice ralentit constamment. On peut le faire en appliquant une décroissance à son playbackRate. On utilise updatePlaybackRate() plutôt que d'affecter directement playbackRate afin d'obtenir une mise à jour fluide :

js
setInterval(() => {
  // S'assurer que le taux de lecture ne descend jamais sous 0.4
  if (redQueenAlice.playbackRate > 0.4) {
    redQueenAlice.updatePlaybackRate(redQueenAlice.playbackRate * 0.9);
  }
  adjustBackgroundPlayback();
}, 1000);

Mais si on les encourage par un clic ou un tap, iels accélèrent en multipliant leur playbackRate :

js
function goFaster() {
  // Un clic ou un tap sur l'écran les fait accélérer
  redQueenAlice.updatePlaybackRate(redQueenAlice.playbackRate * 1.1);
  adjustBackgroundPlayback();
}

document.addEventListener("click", goFaster);
document.addEventListener("touchstart", goFaster);

Les éléments d'arrière-plan ont aussi des playbackRate impactés lors d'un clic ou d'un tap. Leurs taux de lecture dérivent de celui d'Alice, comme ci-dessous. Que se passe-t-il si vous faites courir Alice et la Reine Rouge deux fois plus vite ? Et si vous les laissez ralentir ?

js
/* Alice se fatigue si vite !
  Toutes les quelques secondes, on réduit son taux de lecture pour la ralentir un peu.
*/
const sceneries = [
  foreground1Movement,
  foreground2Movement,
  background1Movement,
  background2Movement,
];

function adjustBackgroundPlayback() {
  // Si Alice et la Reine Rouge courent à une vitesse comprise entre 0.8 et 1.2,
  // l'arrière-plan ne bouge pas.
  // Mais si elles passent sous 0.8, l'arrière-plan glisse en arrière
  if (redQueenAlice.playbackRate < 0.8) {
    sceneries.forEach((anim) => {
      anim.updatePlaybackRate(-redQueenAlice.playbackRate / 2);
    });
  } else if (redQueenAlice.playbackRate > 1.2) {
    sceneries.forEach((anim) => {
      anim.updatePlaybackRate(redQueenAlice.playbackRate / 2);
    });
  } else {
    sceneries.forEach((anim) => {
      anim.updatePlaybackRate(0);
    });
  }
}
adjustBackgroundPlayback();

Rendre persistants les styles d'animation

Lorsqu'on anime des éléments, un cas d'usage courant consiste à conserver l'état final de l'animation après sa fin. Une méthode parfois employée consiste à régler le mode de remplissage sur forwards. Cependant, il n'est pas recommandé d'utiliser les modes de remplissage pour faire persister indéfiniment l'effet d'une animation, pour deux raisons :

Une meilleure approche consiste à utiliser Animation.commitStyles(). Cette méthode écrit les valeurs calculées des styles courants de l'animation dans l'attribut style de son élément cible, après quoi l'élément peut être restylé normalement.

Suppression automatique des animations avec remplissage

Il est possible de déclencher un grand nombre d'animations sur un même élément. Si elles sont indéfinies (c'est-à-dire remplies vers l'avant), cela peut entraîner une liste d'animations énorme, pouvant créer une fuite mémoire. Pour cette raison, les navigateurs suppriment automatiquement les animations avec remplissage après qu'elles ont été remplacées par de nouvelles animations, sauf si la·le développeur·euse indique explicitement de les conserver.

Les animations sont supprimées lorsque toutes les conditions suivantes sont réunies :

  • L'animation est avec remplissage (sa propriété fill est forwards si elle joue en avant, backwards si elle joue en arrière, ou both).
  • L'animation est terminée. (Remarquez qu'à cause de fill, elle est toujours en vigueur.)
  • La chronologie de l'animation est monotone croissante. (C'est toujours vrai pour DocumentTimeline ; d'autres chronologies comme scroll-timeline peuvent reculer.)
  • L'animation n'est pas contrôlée par un balisage déclaratif comme le CSS.
  • Chaque effet de style de l'AnimationEffect de l'animation est surchargé par une autre animation qui satisfait aussi toutes les conditions ci-dessus. (Typiquement, lorsque deux animations définissent la même propriété de style du même élément, la plus récente remplace l'autre.)

Les quatre premières conditions garantissent que, sans intervention de JavaScript, l'effet de l'animation ne changera jamais et ne se terminera jamais. La dernière condition garantit que l'animation n'affecte plus le style d'aucun élément : elle a été totalement remplacée.

Quand l'animation est supprimée automatiquement, l'événement remove est déclenché.

Pour empêcher le navigateur de supprimer automatiquement des animations, appelez la méthode persist() de l'animation.

La propriété replaceState de l'animation vaut removed si l'animation a été supprimée, persisted si vous avez appelé persist() sur l'animation, ou active sinon.

Extraire des informations des animations

Imaginez d'autres usages de playbackRate, comme améliorer l'accessibilité pour les personnes souffrant de troubles vestibulaires en leur permettant de ralentir les animations sur tout un site. Impossible en CSS sans recalculer les durées dans chaque règle, mais avec l'API Web Animations, on peut utiliser Document.getAnimations pour parcourir chaque animation de la page et diviser par deux leur playbackRate, comme suit :

js
document.getAnimations().forEach((animation) => {
  animation.updatePlaybackRate(animation.playbackRate * 0.5);
});

Avec l'API Web Animations, il suffit de changer une seule propriété.

Autre chose difficile à réaliser avec les seules animations CSS : créer des dépendances basées sur des valeurs fournies par d'autres animations. Par exemple, dans l'exemple de jeu « Alice grandit et rétrécit », vous avez peut-être remarqué quelque chose d'étrange concernant la durée du gâteau :

js
document.getElementById("eat-me-sprite").animate([], {
  duration: aliceChange.effect.getComputedTiming().duration / 2,
});

Pour comprendre ce qui se passe ici, regardons l'animation d'Alice :

js
const aliceChange = document
  .getElementById("alice")
  .animate(
    [
      { transform: "translate(-50%, -50%) scale(.5)" },
      { transform: "translate(-50%, -50%) scale(2)" },
    ],
    {
      duration: 8000,
      easing: "ease-in-out",
      fill: "both",
    },
  );

L'animation d'Alice la fait passer de la moitié de sa taille à deux fois sa taille sur 8 secondes. Puis nous la mettons en pause :

js
aliceChange.pause();

Si nous l'avions laissée en pause au début, elle commencerait à la moitié de sa taille finale, comme si elle avait déjà bu toute la bouteille. Nous voulons placer la « tête de lecture » de son animation au milieu, pour qu'elle soit déjà à mi-chemin. Nous pourrions le faire en réglant Animation.currentTime sur 4 secondes, ainsi :

js
aliceChange.currentTime = 4000;

Mais pendant le travail sur cette animation, nous pourrions changer souvent la durée. Mieux vaut donc régler currentTime de manière dynamique, pour ne pas avoir à modifier deux endroits à la fois. C'est possible en se référant à la propriété Animation.effect d'aliceChange, qui renvoie un objet contenant tous les détails de l'effet ou des effets actifs sur Alice :

js
aliceChange.currentTime = aliceChange.effect.getComputedTiming().duration / 2;

effect nous permet d'accéder aux images-clés et aux propriétés de minutage de l'animation — aliceChange.effect.getComputedTiming() pointe vers l'objet de minutage d'Alice — il contient sa duration. On peut la diviser par deux pour obtenir le milieu de la chronologie et régler Alice à une hauteur normale. On peut alors inverser et lire son animation dans un sens comme dans l'autre pour la faire grandir ou rapetisser.

On peut faire de même pour fixer les durées du gâteau et de la bouteille :

js
const drinking = document
  .getElementById("liquid")
  .animate([{ height: "100%" }, { height: "0" }], {
    fill: "forwards",
    duration: aliceChange.effect.getComputedTiming().duration / 2,
  });
drinking.pause();

Les trois animations dépendent maintenant d'une seule durée, que nous pouvons modifier facilement en un seul endroit.

On peut aussi utiliser l'API Web Animations pour déterminer le temps courant d'une animation. La partie se termine lorsque vous n'avez plus de gâteau à manger ou que la bouteille est vide. La vignette présentée dépend de l'avancement d'Alice dans son animation : a-t-elle trop grandi et ne peut-elle plus entrer par la petite porte, ou est-elle trop petite et ne peut-elle pas atteindre la clé pour ouvrir la porte ? Nous pouvons déterminer si elle est du côté « grand » ou « petit » en prenant currentTime et en le divisant par activeDuration :

js
const endGame = () => {
  // obtenir la position de la tête de lecture sur la chronologie d'Alice
  const alicePlayhead = aliceChange.currentTime;
  const aliceTimeline = aliceChange.effect.getComputedTiming().activeDuration;

  // arrêter l'animation d'Alice et les autres
  stopPlayingAlice();

  // selon le tiers dans lequel on se trouve
  const aliceHeight = alicePlayhead / aliceTimeline;

  if (aliceHeight <= 0.333) {
    // Alice a rapetissé !
    // …
  } else if (aliceHeight >= 0.666) {
    // Alice a grandi !
    // …
  } else {
    // Alice n'a pas changé de façon notable
    // …
  }
};

Rappels et promesses

Les animations et transitions CSS ont leurs propres écouteurs d'événements, et c'est aussi possible avec l'API Web Animations :

  • onfinish est le gestionnaire de l'événement finish et peut être déclenché manuellement avec finish().
  • oncancel est le gestionnaire de l'événement cancel et peut être déclenché avec cancel().

Ici, nous configurons les rappels pour le gâteau, la bouteille et Alice afin qu'iels déclenchent la fonction endGame :

js
// Lorsque le gâteau ou la bouteille est terminé
nommingCake.onfinish = endGame;
drinking.onfinish = endGame;

// Alice atteint la fin de son animation
aliceChange.onfinish = endGame;

Mieux encore, l'API Web Animations fournit aussi une promesse finished qui se résout quand l'animation se termine, ou est rejetée si elle est annulée.

Conclusion

Voilà les fonctionnalités de base de l'API Web Animations. Vous devriez désormais être prêt·e à « sauter dans le terrier du lapin » de l'animation dans le navigateur et à écrire vos propres expériences d'animation.

Voir aussi