Il y a trop d'options dans l'API d'animations Web pour les récupérer facilement. Apprendre comment fonctionne le timing 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 d'animations Web 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 d'animations Web et JavaScript. Pour que ce soit clair et éviter la distraction du problème à résoudre, les exemples de code fournis sont simples. Il n'y aura rien de plus complexe que des fonctions et des objets. Comme points d'entrée intéressants 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 CSS Animations vs Web Animations API par Ollie Williams . Nous ne passerons pas en revue les moyens de définir les effets et de les régler pour obtenir le résultat souhaité. Cet article suppose que vos animations sont définies et que vous avez besoin d'idées et de techniques pour les gérer.
Nous commençons par un présentation 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 d'animations Web.
Interfaces
Web Animations API nous donne une nouvelle dimension de contrôle. Avant cela, les transitions et l'animation CSS, tout en fournissant un moyen puissant de définition des effets, avaient toujours un point d'actionnement unique. Comme un interrupteur, il était allumé ou éteint. Vous pouvez jouer avec les délais et les fonctions d'assouplissement pour créer des effets assez complexes. Pourtant, à un certain moment, cela devient encombrant et difficile à travailler. L'interrupteur d'éclairage se transforme en un gradateur avec un curseur. Si vous le souhaitez, vous pouvez en faire toute 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 joue votre animation immédiatement. Il renvoie également une instance d'objet Animation
. Son but est de contrôler la lecture.
Considérez-le comme un lecteur de cassettessi vous vous en souvenez. Je suis conscient que certains lecteurs ne sont peut-être pas familiers avec ce que c'est. Il est inévitable que toute tentative d'appliquer des concepts du monde réel pour décrire des choses informatiques abstraites s'effondre rapidement. Mais qu'on se rassure, lecteur qui ne connaît pas le plaisir de rembobiner une cassette avec un crayon, que les gens qui savent ce qu'est un lecteur de cassettes 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 d'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 cela vous rend les commandes.
Les commandes que vous obtenez sont commodément similaires à celles que vous obtenez des éléments audio et vidéo. Ce sont les méthodes play et pauseet la propriété current time. Avec ces trois commandes, vous pouvez construire n'importe quoi en ce qui concerne la lecture. le temps entre autres. Et c'est ce qu'est le KeyframeEffect
. Notre cassette est quelque chose qui contient tous les enregistrements et les 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'insère dans une nouvelle instance Animation
. Il commence immédiatement à le 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(élément, images clés, options) // (1)
);
animation.play(); (3)
Prenez la cassette (1), mettez-la dans un lecteur (2), puis appuyez sur le bouton Lecture (3).
Le point de savoir comment cela fonctionne dans les coulisses est de pouvoir séparer la définition de images clés et décider quand les jouer. Lorsque vous avez beaucoup d'animations à coordonner, il peut être utile de les rassembler d'abord afin de savoir 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. Il est trop facile de casser l'effet souhaité en faisant glisser quelques images. Dans le cas d'une longue séquence qui traîne s'accumule résultant en une expérience pas du tout convaincante.
Timing
Comme dans la comédie, le timing est tout dans les animations. Pour faire fonctionner un effet, pour obtenir une certaine sensation, vous devez pouvoir affiner la façon dont les propriétés changent. Il existe deux niveaux de synchronisation que vous pouvez contrôler dans l'API d'animations Web.
Au niveau des propriétés individuelles, nous avons offset
. Offset vous donne le contrôle sur la synchronisation d'une propriété unique. En lui donnant une valeur de zéro à un, vous définissez quand chaque effet entre en jeu. 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 de
/à
. C'est ce qu'est 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 commencent au même moment les unes par rapport aux autres.
Comme nous l'avons indiqué précédemment, offset
est un 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 embrouiller, et ce qui m'a définitivement dérouté.
Duration est le durée en millisecondes qu'il faut pour terminer une itération. Elle sera égale à la durée globale par défaut. Une fois que vous avez ajouté un délai ou augmenté le nombre d'itérations dans une animation, la durée cesse de vous indiquer le nombre que vous souhaitez connaître. Il 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 des options de synchronisation. La durée totale de l'animation du début à l'événement « fini » dans l'équation suivante :
délai + (itérations × durée) + délai de fin
Vous pouvez le voir en action dans la démo suivante :
Voir le stylo [What is the actual duration of an animation?](https://codepen.io/smashingmag/pen/jOwyqqd) par Kirill Myshkin.[19659032]Voir le Pen le contexte des médias de longueur fixe. En gardant intacte la durée d'animation souhaitée, vous pouvez la "remplir" avec delay
au début et delayEnd
à la fin afin de l'intégrer dans un contexte de durée plus longue. Si vous y réfléchissez, delay
dans ce sens agirait comme le fait 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 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 le configurer pour commencer à sauter depuis le centre de l'écran et faire en sorte que le nombre soit droit dans l'appareil photo dans le dernière image.
Voir le stylo [Tweak interationStart](https://codepen.io/smashingmag/pen/PojWNze) par Kirill Myshkin.
Contrôler plusieurs comme un
Lorsque je travaillais sur un éditeur d'animation pour une application de présentation, je devais organiser plusieurs animations pour un seul élément sur une timeline. Ma première tentative a été d'utiliser 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 animation particulière de l'interface utilisateur sur la chronologie, elle visait à déplacer sa position de départ sans modifier la durée de l'animation. Avec offset
cela signifiait que je devais changer plusieurs choses, le offset
lui-même et également modifier 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 à comprendre.
Le deuxième problème est venu avec la propriété transform
. En raison du fait qu'il peut représenter plusieurs changements de 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 échelle
et traduisez
. Parfois, il est pratique de définir translate
en pourcentage, c'est-à-dire par rapport à la taille d'un élément. Supposons que vous vouliez qu'une balle saute exactement à trois diamètres de hauteur. Maintenant, selon l'endroit où vous placez la fonction d'échelle – avant ou après le translate
– le résultat change à partir de trois hauteurs de la taille d'origine ou de celle mise à l'échelle.
C'est un trait important de transform
propriété. 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é transform
. Il peut en faire trop assez rapidement. Surtout si vos images clés proviennent d'endroits différents, vous aurez besoin d'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 peut être difficile de comprendre à quoi s'attendre. Pour simplifier cela et conserver la flexibilité, nous devons les séparer en différents canaux.
Une solution consiste à envelopper nos éléments dans div
s que chacun pourrait être animé séparément, par ex. 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 peut sembler que les choses deviennent incontrôlables avec cette astuce. Que nous multiplions le nombre de problèmes que nous avions auparavant. En fait, lorsque j'ai découvert cette astuce pour la première fois, je l'ai rejetée comme étant trop. Je pensais que je pouvais juste m'assurer que ma propriété transform
est compilée à partir de toutes les pièces dans le bon ordre en une seule pièce. Il a fallu une fonction transform
supplémentaire pour rendre les choses trop complexes à gérer et certaines choses impossibles à faire. Mon compilateur de chaîne de propriété transform
a commencé à prendre de plus en plus de temps pour bien fonctionner, alors j'ai abandonné.
Il s'est avéré que contrôler la lecture de plusieurs animations n'est pas si difficile semble être au départ. Vous vous souvenez de l'analogie du lecteur de cassettes avec la mendicité ? Et si vous pouviez utiliser votre propre lecteur qui accepte un nombre illimité 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
:
// Pour jouer, appelez simplement play sur chacune d'entre elles
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 jouées en synchronisation. Il renvoie un objet avec une seule méthode play
.
function createPlayer(animations) {
return Object.freeze({
jouer : fonction () {
animations.forEach((animation) => animation.play());
}
});
}
C'est suffisant pour que vous le sachiez pour commencer à étendre la fonctionnalité. Ajoutons les méthodes pause et currentTime
.
function createPlayer(animations) {
return Object.freeze({
jouer : fonction () {
animations.forEach((animation) => animation.play());
},
pause : fonction () {
animations.forEach((animation) => animation.pause());
},
currentTime : fonction (heure = 0) {
animations.forEach((animation) => animation.currentTime = heure);
}
});
}
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 Animation
est similaire aux interfaces multimédia. En utilisant cette similitude, vous pourriez mettre toutes sortes de choses dans votre lecteur. Pour s'adapter à cela, ajustons 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(fonction (animation) {
if (typeof animation.currentTime === "fonction") {
animation.currentTime(heure);
} autre {
animation.currentTime = heure;
}
});
}
Le lecteur que nous venons de créer est ce qui vous permettra de masquer la complexité de plusieurs div
s pour les canaux d'animation à élément unique. 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 pouvait ê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 à droite. Le second combine l'animation de sauts de tous les contours des balles à gauche et de celui en avant-première.
Enfin, le troisième est un joueur qui combine les animations de position des balles dans un conteneur de gauche . Ce lecteur permet aux balles de se répandre dans une démonstration continue de l'animation avec des tranches d'environ 60 images par seconde. Les navigateurs savent comment rendre rapidement en transmettant le travail au GPU. Avec l'API d'animations Web, 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 doit également être déroutante. Avec une compréhension du contrôle de la synchronisation et de la lecture, vous disposez d'outils pour adapter cette API à vos besoins. Vous devriez pouvoir définir à quel point cela devrait être complexe.
Lectures complémentaires
Source link