Orchestrer la complexité avec l'API Web Animations

Publié: 2022-03-10
Résumé rapide ↬ Il y a beaucoup trop d'options dans l'API Web Animations pour les sélectionner aussi facilement. Apprendre comment fonctionne la synchronisation et comment contrôler la lecture de plusieurs animations à la fois constitue une base solide sur laquelle baser vos projets.

Il n'y a pas de juste milieu entre les transitions simples et les animations complexes. Soit vous êtes d'accord avec ce que fournissent les transitions et les animations CSS, soit vous avez soudainement besoin de toute la puissance que vous pouvez obtenir. L'API Web Animations vous offre de nombreux outils pour travailler avec des animations. Mais il faut savoir les gérer. Cet article vous guidera à travers les principaux points et techniques qui pourraient vous aider à gérer des animations complexes tout en restant flexible.

Avant de plonger dans l'article, il est essentiel que vous connaissiez les bases de l'API Web Animations et de JavaScript. Pour le rendre clair et éviter de détourner l'attention du problème à résoudre, les exemples de code fournis sont clairs. Il n'y aura rien de plus complexe que les fonctions et les objets. Comme bons points d'entrée dans les animations elles-mêmes, je suggérerais MDN comme référence générale, l'excellente série de Daniel C. Wilson et l'API CSS Animations vs Web Animations d'Ollie Williams. Nous n'expliquerons pas comment définir les effets et les régler pour obtenir le résultat souhaité. Cet article suppose que vous avez défini vos animations et que vous avez besoin d'idées et de techniques pour les gérer.

Nous commençons par un aperçu des interfaces et à quoi elles servent. Ensuite, nous examinerons le calendrier et les niveaux de contrôle pour définir quoi, quand et pendant combien de temps. Après cela, nous apprendrons à traiter plusieurs animations comme une seule en les enveloppant dans des objets. Ce serait un bon début pour utiliser l'API Web Animations.

Interfaces

L'API Web Animations nous donne une nouvelle dimension de contrôle. Avant cela, les transitions et animations CSS, tout en fournissant un moyen puissant de définir des effets, avaient toujours un seul point d'actionnement . Comme un interrupteur, il était allumé ou éteint. Vous pouvez jouer avec les retards et les fonctions d'accélération pour créer des effets assez complexes. Pourtant, à un certain moment, cela devient encombrant et difficile à travailler.

L'API Web Animations transforme ce point d'actionnement unique en un contrôle complet sur la lecture . L'interrupteur d'éclairage se transforme en un gradateur avec un curseur. Si vous le souhaitez, vous pouvez en faire l'ensemble de la maison intelligente, car en plus du contrôle de lecture, vous pouvez désormais définir et modifier les effets au moment de l'exécution. Vous pouvez désormais adapter les effets au contexte ou implémenter un éditeur d'animations avec prévisualisation en temps réel.

Nous commençons par l'interface Animation. Pour obtenir un objet d'animation, nous pouvons utiliser la méthode Element.animate . Vous lui donnez des images clés et des options et il lit votre animation immédiatement. Ce qu'il fait également, c'est qu'il renvoie une instance d'objet Animation . Son but est de contrôler la lecture.

Considérez-le comme un lecteur de cassettes , si vous vous en souvenez. Je suis conscient que certains lecteurs pourraient ne pas savoir de quoi il s'agit. Il est inévitable que toute tentative d'appliquer des concepts du monde réel pour décrire des choses informatiques abstraites échouera rapidement. Mais laissez-le vous rassurer - un lecteur qui ne connaît pas la joie de rembobiner une bande avec un crayon - que les gens qui savent ce qu'est un lecteur de cassette seront encore plus confus à la fin de cet article.

Imaginez une boîte. Il a une fente où va la cassette et il a des boutons pour jouer, arrêter et rembobiner. C'est ce qu'est l'instance de l'interface Animation - une boîte qui contient une animation définie et fournit des moyens d'interagir avec sa lecture. Vous lui donnez quelque chose à jouer et il vous redonne le contrôle.

Les commandes que vous obtenez sont similaires à celles que vous obtenez des éléments audio et vidéo. Ce sont les méthodes de lecture et de pause , et la propriété de l' heure actuelle . Avec ces trois commandes, vous pouvez créer n'importe quoi en matière de lecture.

La cassette elle-même est un package qui contient une référence à l'élément animé, la définition des effets et des options qui incluent le timing entre autres. Et c'est ce qu'est le KeyframeEffect . Notre cassette est quelque chose qui contient tous les enregistrements et des informations sur la durée des enregistrements. Je laisserai à l'imagination du public plus âgé le soin de faire correspondre toutes ces propriétés avec les composants d'une cassette physique. Ce que je vais vous montrer, c'est à quoi cela ressemble dans le code.

Lorsque vous créez une animation via Element.animate , vous utilisez un raccourci qui fait trois choses. Il crée une instance KeyframeEffect . Il s'intègre dans une nouvelle instance d' Animation . Il commence immédiatement à jouer.

 const animation = element.animate(keyframes, options);

Décomposons-le et voyons le code équivalent qui fait la même chose.

 const animation = new Animation( // (2) new KeyframeEffect(element, keyframes, options) // (1) ); animation.play(); (3)

Prenez la cassette (1), placez-la dans un lecteur (2), puis appuyez sur le bouton Lecture (3).

L'intérêt de savoir comment cela fonctionne dans les coulisses est de pouvoir séparer la définition des images clés et de décider quand la jouer. Lorsque vous avez beaucoup d'animations à coordonner, il peut être utile de les rassembler toutes en premier afin que vous sachiez qu'elles sont prêtes à jouer. Les générer à la volée et espérer qu'ils commenceraient à jouer au bon moment n'est pas quelque chose que vous voudriez espérer. C'est trop facile de casser l'effet désiré en faisant glisser quelques images. En cas de longue séquence cette traînée s'accumule entraînant une expérience pas du tout convaincante.

Plus après saut! Continuez à lire ci-dessous ↓

Horaire

Comme dans la comédie, le timing est tout dans les animations. Pour qu'un effet fonctionne, pour obtenir une certaine sensation, vous devez être capable d'affiner la façon dont les propriétés changent. Il existe deux niveaux de synchronisation que vous pouvez contrôler dans l'API Web Animations.

Au niveau des propriétés individuelles, nous avons offset . Le décalage vous permet de contrôler la synchronisation d'une seule propriété . En lui donnant une valeur de zéro à un, vous définissez quand chaque effet se déclenche. Lorsqu'il est omis, il est égal à zéro.

Vous vous souvenez peut-être de @keyframes dans CSS comment vous pouvez utiliser des pourcentages au lieu de from / to . C'est ce qu'est l' offset , mais divisé par cent. La valeur de offset est une partie de la durée d'une seule itération .

Le offset vous permet d'organiser les images clés dans un KeyframeEffect . Être un décalage de nombre relatif garantit que, quelle que soit la durée ou la vitesse de lecture, toutes vos images clés démarrent au même moment les unes par rapport aux autres.

Comme nous l'avons indiqué précédemment, le offset est une partie de la durée . Maintenant, je veux que vous évitiez mes erreurs et la perte de temps à ce sujet. Il est important de comprendre que la durée de l'animation n'est pas la même chose que la durée globale d'une animation. Habituellement, ce sont les mêmes et c'est ce qui pourrait vous dérouter, et ce qui m'a certainement dérouté.

La durée est le temps en millisecondes qu'une itération prend pour se terminer. Elle sera égale à la durée globale par défaut. Une fois que vous ajoutez un délai ou augmentez le nombre d'itérations dans une animation, la durée s'arrête en vous indiquant le nombre que vous voulez connaître. C'est important de comprendre pour l'utiliser à votre avantage.

Lorsque vous devez coordonner la lecture d'une image clé dans un contexte plus large, comme la lecture multimédia, vous devez utiliser les options de synchronisation. Toute la durée de l'animation du début à l'événement "terminé" dans l'équation suivante :

 delay + (iterations × duration) + end delay

Vous pouvez le voir en action dans la démo suivante :

Voir le Pen [Quelle est la durée réelle d'une animation ?](https://codepen.io/smashingmag/pen/VwWWrzz) de Kirill Myshkin.

Voir le stylo Quelle est la durée réelle d'une animation ? par Kirill Mychkine.

Ce que cela nous permet de faire, c'est d' aligner plusieurs animations dans le contexte de médias de longueur fixe. En gardant intacte la durée souhaitée de l'animation, vous pouvez la "compléter" avec delay au début et delayEnd à la fin afin de l'intégrer dans un contexte d'une durée plus longue. Si vous y réfléchissez, le delay dans ce sens agirait comme le décalage dans les images clés. N'oubliez pas que le délai est défini en millisecondes, vous pouvez donc le convertir en une valeur relative.

Une autre option de synchronisation qui aiderait à aligner l'animation est iterationStart . Il définit la position de départ d'une itération. Prenez la démo du ballon de billard. En ajustant le curseur iterationStart , vous pouvez définir la position de départ de la balle et la rotation, par exemple, vous pouvez la configurer pour qu'elle commence à sauter depuis le centre de l'écran et que le nombre soit droit dans la caméra dans la dernière image.

Voir le stylo [Tweak interationStart](https://codepen.io/smashingmag/pen/qBjjVPR) de Kirill Myshkin.

Voir l'introduction de Pen Tweak de Kirill Myshkin.

Contrôlez plusieurs comme un

Lorsque j'ai travaillé sur un éditeur d'animation pour une application de présentation, j'ai dû organiser plusieurs animations pour un seul élément sur une chronologie. Ma première tentative a été d'utiliser le offset pour placer mon animation au bon point de départ sur une chronologie.

Cela s'est rapidement avéré être la mauvaise façon d'utiliser offset . En ce qui concerne cette interface utilisateur particulière, déplacer l'animation sur la chronologie signifiait changer sa position de départ sans changer la durée de l'animation. Avec offset , cela signifiait que je devais changer plusieurs choses, le offset lui-même et également changer le offset de la propriété de fermeture pour m'assurer que la durée ne change pas. La solution s'est avérée trop complexe à appréhender.

Le deuxième problème est venu avec la propriété transform . En raison du fait qu'il peut représenter plusieurs changements caractéristiques d'un élément, il peut être difficile de lui faire faire ce que vous voulez. En cas de désir de modifier ces propriétés indépendamment les unes des autres, cela pourrait devenir encore plus difficile. La fonction de changement d'échelle influence toutes les fonctions qui la suivent. Voici pourquoi cela se produit.

La propriété Transform peut prendre plusieurs fonctions dans une séquence comme valeur. Selon l'ordre des fonctions, le résultat change. Prenez scale et translate . Parfois, il est pratique de définir translate en pourcentage, c'est-à-dire relatif à la taille d'un élément. Disons que vous voulez qu'une balle saute exactement trois diamètres de haut. Maintenant, selon l'endroit où vous placez la fonction d'échelle - avant ou après la translate - le résultat change de trois hauteurs de la taille d'origine ou de celle mise à l'échelle.

C'est un trait important de la propriété de transform . Vous en avez besoin pour réaliser une transformation assez complexe. Mais lorsque vous avez besoin que ces transformations soient distinctes et indépendantes des autres transformations d'un élément, cela vous gêne.

Il y a des cas où vous ne pouvez pas mettre tous les effets dans une propriété de transform . Cela peut devenir trop rapidement. Surtout si vos images clés proviennent de différents endroits, vous auriez besoin d'avoir une fusion très complexe d'une chaîne transformée . Vous pouvez difficilement compter sur un mécanisme automatique car la logique n'est pas simple. De plus, il pourrait être difficile de comprendre à quoi s'attendre. Pour simplifier cela et conserver de la flexibilité, nous devons les séparer en différents canaux.

Une solution consiste à envelopper nos éléments dans des div que chacun pourrait être animé séparément, par exemple un div pour le positionnement sur le canevas, un autre pour la mise à l'échelle et un troisième pour la rotation. De cette façon, non seulement vous simplifiez considérablement la définition des animations, mais vous ouvrez également la possibilité de définir différentes origines de transformation, le cas échéant.

Il pourrait sembler que les choses deviennent incontrôlables avec cette astuce. Que nous multiplions le nombre de problèmes que nous avions auparavant. En fait, quand j'ai trouvé cette astuce pour la première fois, je l'ai rejetée comme étant trop. Je pensais que je pouvais simplement m'assurer que ma propriété de transform est compilée à partir de toutes les pièces dans le bon ordre en une seule pièce. Il a fallu une fonction de transform supplémentaire pour rendre les choses trop complexes à gérer et certaines choses impossibles à faire. Mon compilateur de chaînes de propriétés de transform a commencé à prendre de plus en plus de temps pour être correct, alors j'ai abandonné.

Il s'est avéré que contrôler la lecture de plusieurs animations n'est pas si difficile qu'il semble l'être au départ. Vous souvenez-vous de l'analogie du lecteur de cassette depuis le début ? Et si vous pouviez utiliser votre propre lecteur qui prend n'importe quel nombre de cassettes ? Plus que cela, vous pouvez ajouter autant de boutons que vous le souhaitez sur ce lecteur.

La seule différence entre appeler play sur une seule animation et un tableau d'animations est que vous devez itérer. Voici le code que vous pouvez utiliser pour n'importe quelle méthode d'instances Animation :

 // To play just call play on all of them animations.forEach((animation) => animation.play());

Nous allons l'utiliser pour créer toutes sortes de fonctions pour notre lecteur.

Créons cette boîte qui contiendrait les animations et les jouerait. Vous pouvez créer ces boîtes de la manière qui vous convient. Pour que ce soit clair, je vais vous montrer un exemple de le faire avec une fonction et un objet. La fonction createPlayer prend un tableau d'animations qui doivent être lues en synchronisation. Il renvoie un objet avec une méthode de play unique.

 function createPlayer(animations) { return Object.freeze({ play: function () { animations.forEach((animation) => animation.play()); } }); }

Cela vous suffit pour commencer à étendre les fonctionnalités. Ajoutons les méthodes pause et currentTime .

 function createPlayer(animations) { return Object.freeze({ play: function () { animations.forEach((animation) => animation.play()); }, pause: function () { animations.forEach((animation) => animation.pause()); }, currentTime: function (time = 0) { animations.forEach((animation) => animation.currentTime = time); } }); }

Le createPlayer avec ces trois méthodes vous donne suffisamment de contrôle pour orchestrer n'importe quel nombre d'animations . Mais poussons un peu plus loin. Faisons en sorte que notre lecteur puisse non seulement prendre n'importe quel nombre de cassettes, mais aussi d'autres lecteurs.

Comme nous l'avons vu précédemment, l'interface d' Animation est similaire aux interfaces multimédias. En utilisant cette similitude, vous pouvez mettre toutes sortes de choses dans votre lecteur. Pour tenir compte de cela, modifions la méthode currentTime pour qu'elle fonctionne à la fois avec les objets d'animation et les objets provenant de createPlayer .

 function currentTime(time = 0) { animations.forEach(function (animation) { if (typeof animation.currentTime === "function") { animation.currentTime(time); } else { animation.currentTime = time; } }); }

Le lecteur que nous venons de créer est ce qui vous permettra de masquer la complexité de plusieurs div pour les canaux d'animations à un seul élément. Ces éléments pourraient être regroupés dans une scène. Et chaque scène pourrait faire partie de quelque chose de plus grand. Tout cela pourrait être fait avec cette technique.

Pour démontrer la démo de chronométrage, j'ai divisé toutes les animations en trois joueurs. La première consiste à contrôler la lecture de l'aperçu sur la droite. Le second combine l'animation sautante de tous les contours des balles à gauche et de celle en aperçu.

Enfin, le troisième est un joueur qui a combiné des animations de position des balles dans un conteneur de gauche. Ce joueur permet aux balles de se propager dans une démonstration continue de l'animation avec environ 60 images par tranches de seconde.

Conclusion

Les interfaces Web telles que l'API Web Animations exposent pour nous certaines choses que les navigateurs ont toujours faites. Les navigateurs savent comment rendre rapidement en transmettant le travail au GPU. Avec l'API Web Animations, nous en avons le contrôle. Même si ce contrôle peut sembler un peu étranger ou déroutant, cela ne signifie pas que son utilisation devrait également être déroutante. Avec une compréhension du contrôle de la synchronisation et de la lecture, vous disposez d'outils pour apprivoiser cette API selon vos besoins. Vous devriez être en mesure de définir à quel point cela devrait être complexe.

Lectures complémentaires

  • "Techniques pratiques de conception d'animation", Sarah Drasner
  • "Concevoir avec un mouvement réduit pour les sensibilités au mouvement", Val Head
  • "Une interface vocale alternative aux assistants vocaux", Ottomatias Peura
  • "Concevoir de meilleures info-bulles pour les interfaces utilisateur mobiles", Eric Olive