CSS-only Wolfenstein ist ein kleines Projekt, das ich vor ein paar Wochen erstellt habe. Es war ein Experiment mit CSS-3D-Transformationen und Animationen.
Inspiriert von der FPS-Demo und einem weiteren Wolfenstein CodePen, beschloss ich, meine eigene Version zu entwickeln. Sie basiert lose auf Episode 1 – Floor 9 des originalen Wolfenstein 3D-Spiels.
Herausgeber: Dieses Spiel erfordert absichtlich schnelle Reaktionen, um einen Game Over-Bildschirm zu vermeiden.
Hier ist ein Gameplay-Video
Kurz gesagt, mein Projekt ist nichts weiter als eine sorgfältig geskriptete, lange CSS-Animation. Plus ein paar Instanzen des Checkbox-Hacks.
:checked ~ div { animation-name: spin; }
Die Umgebung besteht aus 3D-Gitterflächen und die Animationen sind meist einfache 3D-Translationen und -Rotationen. Nichts wirklich Besonderes.
Zwei Probleme waren jedoch besonders schwierig zu lösen
- Spiele die „Waffenfeuer“-Animation ab, wann immer der Spieler auf einen Feind klickt.
- Wenn der sich schnell bewegende Boss den letzten Treffer landete, trete in eine dramatische Zeitlupe ein.
Auf technischer Ebene bedeutete dies
- Spiele eine Animation erneut ab, wenn die nächste Checkbox aktiviert wird.
- Verlangsame eine Animation, wenn eine Checkbox aktiviert wird.
Tatsächlich war keines davon in meinem Projekt richtig gelöst! Ich habe entweder Workarounds verwendet oder einfach aufgegeben.
Andererseits habe ich nach einigem Recherchieren schließlich den Schlüssel zu beiden Problemen gefunden: das Ändern der Eigenschaften von laufenden CSS-Animationen. In diesem Artikel werden wir dieses Thema weiter untersuchen
- Viele interaktive Beispiele.
- Zerlegungen: Wie funktioniert jedes Beispiel (oder auch nicht)?
- Hinter den Kulissen: Wie verarbeiten Browser Animationszustände?
Ich werde mal "meine Ziegel werfen".
Problem 1: Erneutes Abspielen einer Animation
Das erste Beispiel: „Nur eine weitere Checkbox“
Meine erste Intuition war „fügen Sie einfach eine weitere Checkbox hinzu“, was nicht funktioniert
Jede Checkbox funktioniert einzeln, aber nicht beide zusammen. Wenn eine Checkbox bereits aktiviert ist, funktioniert die andere nicht mehr.
So funktioniert es (oder „funktioniert nicht“)
- Der
animation-namevon<div>ist standardmäßignone. - Der Benutzer klickt auf eine Checkbox,
animation-namewird zuspinund die Animation beginnt von vorne. - Nach einer Weile klickt der Benutzer auf die andere Checkbox. Eine neue CSS-Regel tritt in Kraft, aber
animation-nameist *immer noch*spin, was bedeutet, dass keine Animation hinzugefügt oder entfernt wird. Die Animation spielt einfach weiter, als wäre nichts geschehen.
Das zweite Beispiel: „Klonen der Animation“
Ein funktionierender Ansatz ist, die Animation zu klonen
#spin1:checked ~ div { animation-name: spin1; }
#spin2:checked ~ div { animation-name: spin2; }
So funktioniert es
animation-nameist zunächstnone.- Der Benutzer klickt auf „Spin!“,
animation-namewird zuspin1. Die Animationspin1startet von vorne, da sie gerade hinzugefügt wurde. - Der Benutzer klickt auf „Spin again!“,
animation-namewird zuspin2. Die Animationspin2startet von vorne, da sie gerade hinzugefügt wurde.
Beachten Sie, dass in Schritt Nr. 3 spin1 aufgrund der Reihenfolge der CSS-Regeln entfernt wird. Es funktioniert nicht, wenn „Spin again!“ zuerst aktiviert wird.
Das dritte Beispiel: „Anhängen derselben Animation“
Ein weiterer funktionierender Ansatz ist das „Anhängen derselben Animation“
#spin1:checked ~ div { animation-name: spin; }
#spin2:checked ~ div { animation-name: spin, spin; }
Dies ist ähnlich wie im vorherigen Beispiel. Sie können das Verhalten tatsächlich auf diese Weise verstehen
#spin1:checked ~ div { animation-name: spin1; }
#spin2:checked ~ div { animation-name: spin2, spin1; }
Beachten Sie, dass beim Anklicken von „Spin again!“ die alte laufende Animation zur zweiten Animation in der neuen Liste wird, was unintuitiv sein kann. Eine direkte Folge davon ist: Der Trick funktioniert nicht, wenn animation-fill-mode auf forwards gesetzt ist. Hier ist eine Demo
Wenn Sie sich fragen, warum das so ist, hier sind einige Hinweise
animation-fill-modeist standardmäßignone, was bedeutet „Die Animation hat keinerlei Auswirkung, wenn sie nicht abgespielt wird“.animation-fill-mode: forwards;bedeutet „Nachdem die Animation abgespielt wurde, muss sie für immer im letzten Keyframe verharren“.spin1s Entscheidung überschreibt immerspin2s, daspin1später in der Liste erscheint.- Angenommen, der Benutzer klickt auf „Spin!“, wartet auf eine volle Drehung und klickt dann auf „Spin again!“. Zu diesem Zeitpunkt ist
spin1bereits beendet undspin2beginnt gerade.
Diskussion
Faustregel: Sie können eine bestehende CSS-Animation nicht „neu starten“. Stattdessen möchten Sie eine neue Animation hinzufügen und abspielen. Dies wird möglicherweise durch die W3C-Spezifikation bestätigt
Sobald eine Animation gestartet ist, läuft sie weiter, bis sie endet oder
animation-nameentfernt wird.
Wenn wir die letzten beiden Beispiele vergleichen, glaube ich, dass in der Praxis das „Klonen von Animationen“ oft besser funktionieren sollte, insbesondere wenn ein CSS-Präzessor verfügbar ist.
Problem 2: Zeitlupe
Man könnte denken, dass das Verlangsamen einer Animation nur eine Frage der Einstellung einer längeren animation-duration ist
div { animation-duration: 0.5s; }
#slowmo:checked ~ div { animation-duration: 1.5s; }
Tatsächlich funktioniert das
... oder etwa nicht?
Mit ein paar Anpassungen sollte es einfacher sein, das Problem zu erkennen.
Ja, die Animation wird verlangsamt. Und nein, es sieht nicht gut aus. Der Hund „springt“ (fast) immer, wenn Sie die Checkbox umschalten. Außerdem scheint der Hund zu einer zufälligen Position statt zur Anfangsposition zu springen. Wie kommt das?
Es wäre einfacher zu verstehen, wenn wir zwei „Schattenelemente“ einführen würden
Beide Schattenelemente führen dieselben Animationen mit unterschiedlicher animation-duration aus. Und sie werden nicht von der Checkbox beeinflusst.
Wenn Sie die Checkbox umschalten, wechselt das Element einfach sofort zwischen den Zuständen der beiden Schattenelemente.
Zitat der W3C-Spezifikation
Änderungen der Werte von Animationseigenschaften, während die Animation läuft, gelten so, als hätte die Animation diese Werte seit Beginn gehabt.
Dies folgt dem zustandslosen Design, das es Browsern ermöglicht, den animierten Wert leicht zu ermitteln. Die tatsächliche Berechnung wird hier und hier beschrieben.
Ein weiterer Versuch
Eine Idee ist, die aktuelle Animation zu pausieren und dann eine langsamere Animation hinzuzufügen, die von dort übernimmt
div {
animation-name: spin1;
animation-duration: 2s;
}
#slowmo:checked ~ div {
animation-name: spin1, spin2;
animation-duration: 2s, 5s;
animation-play-state: paused, running;
}
Es funktioniert also
... oder etwa nicht?
Es verlangsamt sich, wenn Sie auf „Slowmo!“ klicken. Wenn Sie jedoch eine volle Umdrehung warten, sehen Sie einen „Sprung“. Tatsächlich springt es immer an die Position, an der „Slowmo!“ angeklickt wurde.
Der Grund ist, dass wir keinen from-Keyframe definiert haben – und das sollten wir auch nicht. Wenn der Benutzer auf „Slowmo!“ klickt, wird spin1 an einer bestimmten Position pausiert und spin2 startet an genau derselben Position. Wir können diese Position nicht vorhersehen… oder doch?
Eine funktionierende Lösung
Das können wir! Durch die Verwendung einer benutzerdefinierten Eigenschaft können wir den Winkel in der ersten Animation erfassen und ihn dann an die zweite Animation übergeben
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);
}
}
Hinweis: @property wird in diesem Beispiel verwendet, was nicht von allen Browsern unterstützt wird.
Die „perfekte“ Lösung
Die vorherige Lösung hat einen Nachteil: „Zeitlupe verlassen“ funktioniert nicht gut.
Hier ist eine bessere Lösung
In dieser Version kann die Zeitlupe nahtlos ein- und ausgeschaltet werden. Es werden auch keine experimentellen Features verwendet. Ist es also die perfekte Lösung? Ja und nein.
Diese Lösung funktioniert wie das „Schalten“ von „Gängen“
- Gänge: Es gibt zwei
<div>s. Einer ist das Elternelement des anderen. Beide haben diespin-Animation, aber mit unterschiedlicheranimation-duration. Der Endzustand des Elements ist die Akkumulation beider Animationen. - Schalten: Am Anfang läuft nur die Animation eines
<div>s. Das andere ist pausiert. Wenn die Checkbox umgeschaltet wird, tauschen beide Animationen ihre Zustände.
Obwohl mir das Ergebnis sehr gut gefällt, gibt es ein Problem: Es ist eine nette Ausnutzung der spin-Animation, die im Allgemeinen nicht für andere Animationstypen funktioniert.
Eine praktische Lösung (mit JS)
Für allgemeine Animationen ist es möglich, die Zeitlupenfunktion mit ein wenig JavaScript zu erreichen
Eine kurze Erklärung
- Eine benutzerdefinierte Eigenschaft wird verwendet, um den Animationsfortschritt zu verfolgen.
- Die Animation wird neu gestartet, wenn die Checkbox umgeschaltet wird.
- Der JS-Code berechnet die korrekte
animation-delay, um einen nahtlosen Übergang zu gewährleisten. Ich empfehle diesen Artikel, wenn Sie mit negativen Werten vonanimation-delaynicht vertraut sind.
Sie können diese Lösung als Hybrid aus „Neustart der Animation“ und dem „Gangschaltungs“-Ansatz betrachten.
Hier ist es wichtig, den Animationsfortschritt korrekt zu verfolgen. Workarounds sind möglich, wenn @property nicht verfügbar ist. Als Beispiel verwendet diese Version z-index, um den Fortschritt zu verfolgen
Nebenbemerkung: Ursprünglich habe ich auch versucht, eine reine CSS-Version zu erstellen, war aber nicht erfolgreich. Obwohl nicht 100% sicher, glaube ich, dass dies daran liegt, dass animation-delay nicht animierbar ist.
Hier ist eine Version mit minimalem JavaScript. Nur das „Betreten der Zeitlupe“ funktioniert.
Bitte lassen Sie mich wissen, wenn Sie eine funktionierende reine CSS-Version erstellen können!
Zeitlupe für jede Animation (mit JS)
Zuletzt möchte ich eine Lösung teilen, die für (fast) jede Animation funktioniert, auch mit mehreren komplizierten @keyframes
Grundsätzlich müssen Sie einen Tracker für den Animationsfortschritt hinzufügen und dann sorgfältig animation-delay für die neue Animation berechnen. Manchmal kann es jedoch schwierig (aber möglich) sein, die richtigen Werte zu erhalten.
Zum Beispiel:
animation-timing-functionist nichtlinear.animation-directionist nichtnormal.- mehrere Werte in
animation-namemit unterschiedlichenanimation-durationundanimation-delay.
Diese Methode wird auch hier für die Web Animations API beschrieben.
Danksagungen
Ich begann diesen Weg, nachdem ich auf reine CSS-Projekte gestoßen war. Einige waren filigrane Kunstwerke und einige waren komplexe Konstruktionen. Meine Favoriten sind diejenigen, die 3D-Objekte beinhalten, zum Beispiel dieser hüpfende Ball und dieser verpackte Würfel.
Am Anfang hatte ich keine Ahnung, wie diese gemacht wurden. Später las ich und lernte viel aus diesem schönen Tutorial von Ana Tudor.
Wie sich herausstellte, ist das Erstellen und Animieren von 3D-Objekten mit CSS nicht viel anders als mit Blender, nur mit einem etwas anderen Geschmack.
Fazit
In diesem Artikel haben wir das Verhalten von CSS-Animationen untersucht, wenn eine animate-*-Eigenschaft geändert wird. Insbesondere haben wir Lösungen für „erneutes Abspielen einer Animation“ und „Animations-Zeitlupe“ erarbeitet.
Ich hoffe, Sie finden diesen Artikel interessant. Bitte lassen Sie mich Ihre Gedanken wissen!
Interessant, danke, dass Sie Ihre Erfahrung teilen. Für Problem Nr. 2 könnten Sie an
document.getAnimations()undanimation.playbackRateinteressiert sein.Danke! Es wäre praktischer, wenn es auch ein CSS-Gegenstück gäbe.
Es könnte in CSS Animations 2 definiert sein.
Schön!