Animer des pages web
Aperçu
Les animations web échouent de deux manières : elles ne s'exécutent pas, ou elles s'exécutent mal. Chacune a des causes spécifiques et identifiables. Deviner les courbes d'accélération est la dernière chose à faire, pas la première.
Principe fondamental : avant de régler une animation, prouvez qu'elle s'applique bien aux éléments que vous ciblez.
Pour la conception de scènes épinglées et synchronisées au défilement, voir la compétence choreographing-scroll-stories. Celle-ci traite de la mécanique : faire en sorte que l'animation s'exécute et s'exécute correctement.
Quand une animation ne s'exécute pas du tout
Vérifiez dans cet ordre. Les trois premiers points expliquent la quasi-totalité des cas.
| Symptôme | Cause | Vérification |
|---|---|---|
| Rien ne bouge, éléments visibles | Le sélecteur ne correspond à aucun élément | document.querySelectorAll(sel).length dans la console |
| Rien ne bouge, éléments invisibles | La divulgation a défini opacity: 0 et le déclencheur ne s'est jamais activé | opacity calculée sur l'élément |
| Un fondu enchaîné ne se produit jamais | Les deux calques sont à opacity: 1 ; seule la règle "activé" existe | Il doit exister une règle de repos définissant le calque entrant à 0 |
| Se déclenche immédiatement au chargement | Le déclencheur a été évalué avant la stabilisation du layout | Exécuter après load, pas DOMContentLoaded, lorsque les images dimensionnent la page |
| Se déclenche une fois puis plus jamais | once: true combiné à un élément qui rentre à nouveau | Est-ce intentionnel ? |
| Rien ne s'exécute dans un onglet masqué ou un volet sans tête | Le ticker est en pause quand document.hidden | Forcer : gsap.ticker.lagSmoothing(0); gsap.ticker.tick() |
| Les déclencheurs sont décalés de quelques centaines de pixels | Les images ont été chargées après la mesure des déclencheurs | ScrollTrigger.refresh() au chargement et au redimensionnement (debounce de 200 ms) |
Le cas du fondu enchaîné mérite d'être détaillé car il semble correct lors d'une revue de code :
/* Ceci seul ne fait rien. */
.tile.is-swapped .layer.is-alt { opacity: 1 }
/* L'état de repos est ce qui en fait une animation. */
.layer.is-alt { opacity: 0 }
.tile.is-swapped .layer.is-alt { opacity: 1 }
Intros et animations ambiantes
- Le titre doit être visible rapidement. Réduisez une intro pour que le titre apparaisse en environ 1,1 s. Une intro de 3,8 s est un écran de chargement.
- Les photos changent par un fondu de sortie d'une cellule et un fondu d'entrée dans une autre. Elles ne glissent jamais. Chaque photo est un seul élément, jamais affiché deux fois à l'écran, et jamais en fondu de sortie pendant que sa copie entre en fondu.
- Le changement ambiant est lent et irrégulier. Un carreau toutes les 1,8 à 3,4 s donne l'impression d'une grille vivante, tandis qu'un seul carreau change lentement. Ne réduisez pas l'intervalle pour accélérer le changement de chaque carreau.
- Mélanger par rounds. Chaque carreau change une fois avant que le cycle ne se répète, et chaque sélection provient de la moitié la plus éloignée des trois derniers.
- Mettre en pause ce que personne ne peut voir. Les boucles ambiantes s'arrêtent hors écran et dans un onglet masqué, et reprennent là où elles s'étaient arrêtées.
- Les intros ne se rejouent pas à chaque retour. Une fois l'intro terminée, seule la couche ambiante bouge.
Révélations au défilement
- Ne définissez
opacity: 0que sur les éléments qui sont réellement en dessous de la ligne de flottaison au chargement. Masquer systématiquement tout signifie que tout ce qui se trouve au-dessus est invisible jusqu'à un défilement qui n'aura peut-être jamais lieu. - Révélez à
top 88%, pas à l'intersection complète. Attendre que l'élément soit totalement dans la vue signifie qu'il s'anime après que le lecteur le regarde déjà. - Échelonnez entre les frères, pas à l'intérieur d'un composant. 60-90 ms entre les cartes, 0 à l'intérieur d'une carte.
- Regroupez avec un observateur par groupe plutôt qu'un par élément.
Vidéo
Une vidéo de fond en lecture automatique a un bogue universel : la vidéo joue hors écran, un observateur la met en pause, et au moment où le lecteur arrive, la boucle a 15 secondes de retard et le battement d'ouverture est perdu.
Supprimez complètement l'attribut autoplay.
appeler pause() et load() à l'initialisation.
À la première intersection uniquement : currentTime = 0, puis play().
Servez un fichier 1x en dessous de 992 px ou sous un ratio de pixels de 1,5, et une image de substitution pour le rendu avant le décodage de la vidéo. La vidéo lourde en page d'accueil consomme aussi de la bande passante : auditez-la.
Saccades
Un lent mouvement de dérive sur une photo fixe est la cause la plus fréquente. Un effet Ken Burns de moins d'environ 1 px par image rééchantillonne l'image à chaque image et donne l'impression de scintillement, pas de mouvement.
- Soit bougez assez pour que ce soit un mouvement, soit ne bougez pas du tout.
- Pour un arrêt dans une vidéo rendue, copiez l'image encodée octet par octet plutôt que de ré-encoder une image identique. Toute autre méthode dérive.
- Vérifiez la jointure de la boucle. Si l'image 0 et la dernière image diffèrent, la boucle saute visiblement à chaque cycle. Mesurez la différence absolue moyenne entre elles.
- Ne doublez pas le lissage. Une bibliothèque de défilement fluide associée à une valeur
scrubélevée produit une page qui traîne derrière la molette. Si une bibliothèque de défilement fluide est présente, utilisezscrub: 0.2.
Éléments collants
position: sticky à l'intérieur d'une grille CSS se résout par rapport à la zone de la grille, pas au conteneur entier. Un élément collant qui n'est pas plus haut que son contenu n'a pas de déplacement et se libère immédiatement.
.grid { align-items: stretch } /* la zone fait maintenant toute la hauteur */
.col { position: static; height: 100% }
.inner { position: sticky; top: 10rem }
Mécanismes de repli
Chaque animation bénéficie de trois mécanismes de repli, documentés dans le cahier des charges de l'animation et testés :
- Mouvement réduit.
prefers-reduced-motion: reduceet une requête?staticrendent les états empilés et finaux : rien d'épinglé, rien de masqué, chaque scène dans son état final. Couvrez également chaquetransitionCSS que vous avez ajoutée. Lister les sélecteurs animés mais oublier la transition sur le conteneur laisse une couleur qui se déplace toujours. - Pas de script. Tout reste visible. Une classe est ajoutée après environ 3,5 s si le script n'a jamais démarré, et le CSS révèle tout ce qui se trouve en dessous.
- Téléphones. Les scènes épinglées s'empilent. Documentez-le, testez-le.
Vérification
Ne signalez pas une animation comme fonctionnant à partir du code. Confirmez dans la page :
- Les compteurs de sélecteurs sont non nuls pour chaque groupe animé
- La propriété calculée
opacitysur un élément au-dessus de la ligne de flottaison est 1 avant tout défilement - La valeur
currentTimede la vidéo est inférieure à 0,5 s au moment où elle devient visible pour la première fois - L'émulation de mouvement réduit laisse la page entièrement lisible et statique
- L'absence de script laisse tout visible
- Rien n'anime
width,height,topouleft; utiliseztransformetopacity - Les boucles ambiantes n'effectuent aucun changement de style hors écran
Drapeaux rouges
- Ajuster la durée ou l'accélération avant de confirmer que le sélecteur correspond
- Une règle
.is-activesans règle d'état de repos correspondante transition: all- Une animation ajoutée parce que la section "semblait vide"
- Une photo qui glisse
- La même photo affichée deux fois à l'écran