Piratage de l'état d'animation CSS et du temps de lecture PlatoBlockchain Data Intelligence. Recherche verticale. Aï.

Piratage de l'état de l'animation CSS et du temps de lecture

Wolfenstein CSS uniquement est un petit projet que j'ai fait il y a quelques semaines. C'était une expérience avec des transformations et des animations CSS 3D.

Inspiré par le Démo FPS et un autre Stylo code Wolfenstein, j'ai décidé de construire ma propre version. Il est vaguement basé sur l'épisode 1 - Étage 9 du jeu Wolfenstein 3D original.

Éditeur : Ce jeu nécessite intentionnellement une réaction rapide pour éviter un écran Game Over.

Voici une vidéo de lecture :

[Contenu intégré]

En un mot, mon projet n'est rien d'autre qu'une longue animation CSS soigneusement scénarisée. Plus quelques exemples de pirater la case à cocher.

:checked ~ div { animation-name: spin; }

L'environnement se compose de faces de grille 3D et les animations sont principalement des traductions et des rotations 3D simples. Rien de vraiment chic.

Cependant, deux problèmes étaient particulièrement délicats à résoudre :

  • Jouez l'animation "tir d'arme" chaque fois que le joueur clique sur un ennemi.
  • Lorsque le boss rapide a reçu le dernier coup, entrez un ralenti spectaculaire.

Au niveau technique, cela signifiait :

  • Rejouer une animation lorsque la case suivante est cochée.
  • Ralentir une animation, lorsqu'une case est cochée.

En fait, ni l'un ni l'autre n'a été correctement résolu dans mon projet ! Soit j'ai fini par utiliser des solutions de contournement, soit j'ai simplement abandonné.

D'un autre côté, après quelques recherches, j'ai finalement trouvé la clé des deux problèmes : modifier les propriétés des animations CSS en cours d'exécution. Dans cet article, nous approfondirons ce sujet :

  • Beaucoup d'exemples interactifs.
  • Dissections : comment chaque exemple fonctionne (ou ne fonctionne pas) ?
  • Derrière la scène : comment les navigateurs gèrent-ils les états d'animation ?

Laisse moi "lancer mes briques".

Problème 1 : rejouer l'animation

Le premier exemple : "juste une autre case à cocher"

Ma première intuition a été "ajoutez simplement une autre case à cocher", ce qui ne fonctionne pas :

Chaque case à cocher fonctionne individuellement, mais pas les deux ensemble. Si une case est déjà cochée, l'autre ne fonctionne plus.

Voici comment cela fonctionne (ou "ne fonctionne pas") :

  1. La animation-name of <div> is none par défaut.
  2. L'utilisateur clique sur une case à cocher, animation-name devient spin, et l'animation recommence depuis le début.
  3. Au bout d'un moment, l'utilisateur clique sur l'autre case à cocher. Une nouvelle règle CSS prend effet, mais animation-name is toujours spin, ce qui signifie qu'aucune animation n'est ajoutée ni supprimée. L'animation continue simplement à jouer comme si de rien n'était.

Le deuxième exemple : "clonage de l'animation"

Une approche de travail consiste à cloner l'animation :

#spin1:checked ~ div { animation-name: spin1; }
#spin2:checked ~ div { animation-name: spin2; }

Voici comment cela fonctionne:

  1. animation-name is none initialement.
  2. L'utilisateur clique sur "Spin!", animation-name devient spin1. L'animation spin1 est démarré depuis le début car il vient d'être ajouté.
  3. L'utilisateur clique sur "Spin again!", animation-name devient spin2. L'animation spin2 est démarré depuis le début car il vient d'être ajouté.

Notez qu'à l'étape 3, spin1 est supprimé en raison de l'ordre des règles CSS. Cela ne fonctionnera pas si "Spin again!" est vérifié en premier.

Le troisième exemple : "ajouter la même animation"

Une autre approche de travail consiste à "ajouter la même animation":

#spin1:checked ~ div { animation-name: spin; }
#spin2:checked ~ div { animation-name: spin, spin; }

Ceci est similaire à l'exemple précédent. Vous pouvez réellement comprendre le comportement de cette façon :

#spin1:checked ~ div { animation-name: spin1; }
#spin2:checked ~ div { animation-name: spin2, spin1; }

Notez que lorsque "Spin again!" est cochée, l'ancienne animation en cours devient la deuxième animation de la nouvelle liste, ce qui peut ne pas être intuitif. Une conséquence directe est : l'astuce ne fonctionnera pas si animation-fill-mode is forwards. Voici une démo :

Si vous vous demandez pourquoi c'est le cas, voici quelques indices :

  • animation-fill-mode is none par défaut, ce qui signifie "L'animation n'a aucun effet si elle n'est pas en cours de lecture".
  • animation-fill-mode: forwards; signifie "Après la fin de la lecture de l'animation, elle doit rester à la dernière image clé pour toujours".
  • spin1la décision de l'emporte toujours spin2c'est parce que spin1 apparaît plus tard dans la liste.
  • Supposons que l'utilisateur clique sur "Spin !", attend un tour complet, puis clique sur "Spin again!". À ce moment là. spin1 est déjà terminé, et spin2 commence juste.

a lieu

Règle de base : vous ne pouvez pas "redémarrer" une animation CSS existante. Au lieu de cela, vous souhaitez ajouter et lire une nouvelle animation. Cela peut être confirmé par la spécification W3C:

Une fois qu'une animation a commencé, elle continue jusqu'à ce qu'elle se termine ou que le nom de l'animation soit supprimé.

En comparant maintenant les deux derniers exemples, je pense qu'en pratique, le "clonage d'animations" devrait souvent mieux fonctionner, en particulier lorsque le préprocesseur CSS est disponible.

Problème 2 : Ralenti

On pourrait penser que le ralentissement d'une animation est juste une question de définir un plus long animation-duration:

div { animation-duration: 0.5s; }
#slowmo:checked ~ div { animation-duration: 1.5s; }

En effet, cela fonctionne :

… ou le fait-il ?

Avec quelques ajustements, il devrait être plus facile de voir le problème.

Oui, l'animation est ralentie. Et non, ça n'a pas l'air bien. Le chien "saute" (presque) toujours lorsque vous cochez la case. De plus, le chien semble sauter vers une position aléatoire plutôt que vers la position initiale. Comment venir?

Il serait plus facile de le comprendre si nous introduisions deux "éléments d'ombre":

Les deux éléments d'ombre exécutent les mêmes animations avec différents animation-duration. Et ils ne sont pas affectés par la case à cocher.

Lorsque vous cochez la case, l'élément bascule immédiatement entre les états de deux éléments d'ombre.

Citant la spécification W3C:

Les modifications apportées aux valeurs des propriétés d'animation pendant l'exécution de l'animation s'appliquent comme si l'animation avait ces valeurs depuis le début.

Cela suit la apatride design, qui permet aux navigateurs de déterminer facilement la valeur animée. Le calcul réel est décrit ici ainsi que ici.

Une autre tentative

Une idée est de mettre en pause l'animation en cours, puis d'ajouter une animation plus lente qui prend le relais :

div {
  animation-name: spin1;
  animation-duration: 2s;
}

#slowmo:checked ~ div {
  animation-name: spin1, spin2;
  animation-duration: 2s, 5s;
  animation-play-state: paused, running;
}

Donc ça marche :

… ou le fait-il ?

Il ralentit lorsque vous cliquez sur "Slowmo!". Mais si vous attendez un cercle complet, vous verrez un "saut". En fait, il saute toujours à la position où "Slowmo!" est cliqué.

La raison en est que nous n'avons pas de from keyframe défini - et nous ne devrions pas. Lorsque l'utilisateur clique sur "Slowmo !", spin1 est en pause à une certaine position, et spin2 commence exactement à la même position. Nous ne pouvons tout simplement pas prédire cette position à l'avance… ou le pouvons-nous ?

Une solution de travail

Nous pouvons! En utilisant une propriété personnalisée, nous pouvons capturer l'angle dans la première animation, puis le transmettre à la seconde animation :

div {
  transform: rotate(var(--angle1));
  animation-name: spin1;
  animation-duration: 2s;
}

#slowmo:checked ~ div {
  transform: rotate(var(--angle2));
  animation-name: spin1, spin2;
  animation-duration: 2s, 5s;
  animation-play-state: paused, running;
}

@keyframes spin1 {
  to {
    --angle1: 360deg;
  }
}

@keyframes spin2 {
  from {
    --angle2: var(--angle1);
  }
  to {
    --angle2: calc(var(--angle1) + 360deg);
  }
}

Remarque: @property est utilisé dans cet exemple, qui est pas pris en charge par tous les navigateurs.

La solution "parfaite"

Il y a une mise en garde à la solution précédente : "quitter slowmo" ne fonctionne pas bien.

Voici une meilleure solution :

Dans cette version, le ralenti peut être entré ou quitté de manière transparente. Aucune fonctionnalité expérimentale n'est utilisée non plus. Alors est-ce la solution idéale ? Oui et non.

Cette solution fonctionne comme un "changement de vitesse" :

  • Engrenages : il y en a deux <div>s. L'un est le parent de l'autre. Les deux ont le spin animation mais avec différents animation-duration. L'état final de l'élément est l'accumulation des deux animations.
  • Changement de vitesse : Au début, un seul <div> a son animation en cours d'exécution. L'autre est en pause. Lorsque la case est cochée, les deux animations permutent leurs états.

Même si j'aime beaucoup le résultat, il y a un problème : c'est un bel exploit du spin animation, qui ne fonctionne pas pour les autres types d'animations en général.

Une solution pratique (avec JS)

Pour les animations générales, il est possible de réaliser la fonction slow motion avec un peu de JavaScript :

Une explication rapide:

  • Une propriété personnalisée est utilisée pour suivre la progression de l'animation.
  • L'animation est "redémarrée" lorsque la case est cochée.
  • Le code JS calcule le bon animation-delay pour assurer une transition en douceur. je recommande cet article si vous n'êtes pas familier avec les valeurs négatives de animation-delay.

Vous pouvez considérer cette solution comme un hybride de "redémarrage de l'animation" et de l'approche de "changement de vitesse".

Ici, il est important de suivre correctement la progression de l'animation. Des solutions de contournement sont possibles si @property n'est pas disponible. Par exemple, cette version utilise z-index pour suivre l'évolution :

Side-note : à l'origine, j'ai également essayé de créer une version CSS uniquement, mais je n'ai pas réussi. Bien que je ne sois pas sûr à 100 %, je pense que c'est parce que animation-delay is non animable.

Voici une version avec un minimum de JavaScript. Seul "entrer au ralenti" fonctionne.

N'hésitez pas à me faire savoir si vous parvenez à créer une version fonctionnant uniquement en CSS !

Ralenti n'importe quelle animation (avec JS)

Enfin, j'aimerais partager une solution qui fonctionne pour (presque) n'importe quelle animation, même avec plusieurs complexes @keyframes:

Fondamentalement, vous devez ajouter un suivi de la progression de l'animation, puis calculer soigneusement animation-delay pour la nouvelle animation. Cependant, il peut parfois être difficile (mais possible) d'obtenir les valeurs correctes.

Par exemple :

  • animation-timing-function n'est pas linear.
  • animation-direction n'est pas normal.
  • plusieurs valeurs dans animation-name avec différents animation-duration'le sable animation-delay'S.

Cette méthode est également décrite ici pour le API d'animations Web.

Remerciements

J'ai commencé dans cette voie après avoir rencontré des projets CSS uniquement. Certains étaient oeuvre délicate, et certains étaient engins complexes. Mes préférés sont ceux impliquant des objets 3D, par exemple, ce balle bondissante et cette cube d'emballage.

Au début, je n'avais aucune idée de la façon dont ils étaient fabriqués. Plus tard, j'ai lu et appris beaucoup de ce joli tuto d'Ana Tudor.

Il s'est avéré que construire et animer des objets 3D avec CSS n'est pas très différent de le faire avec Mixeur, juste avec une saveur un peu différente.

Conclusion

Dans cet article, nous avons examiné le comportement des animations CSS lorsqu'un animate-* la propriété est altérée. Nous avons notamment élaboré des solutions pour « rejouer une animation » et « animation au ralenti ».

J'espère que vous trouverez cet article intéressant. S'il vous plaît laissez-moi savoir vos pensées!

Horodatage:

Plus de Astuces CSS