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 :
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") :
- La
animation-name
of<div>
isnone
par défaut. - L'utilisateur clique sur une case à cocher,
animation-name
devientspin
, et l'animation recommence depuis le début. - Au bout d'un moment, l'utilisateur clique sur l'autre case à cocher. Une nouvelle règle CSS prend effet, mais
animation-name
is toujoursspin
, 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:
animation-name
isnone
initialement.- L'utilisateur clique sur "Spin!",
animation-name
devientspin1
. L'animationspin1
est démarré depuis le début car il vient d'être ajouté. - L'utilisateur clique sur "Spin again!",
animation-name
devientspin2
. L'animationspin2
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
isnone
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".spin1
la décision de l'emporte toujoursspin2
c'est parce quespin1
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é, etspin2
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 lespin
animation mais avec différentsanimation-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 deanimation-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 paslinear
.animation-direction
n'est pasnormal
.- plusieurs valeurs dans
animation-name
avec différentsanimation-duration
'le sableanimation-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!