Zeitbasierte CSS-Animationstechniken
(yuanchuan.dev)- Mit der erweiterten Unterstützung für CSS-Mathefunktionen wird ein Ansatz wieder praktikabel, bei dem der Animationszustand statt über Keyframes über die Zeit-Tick-Variable
--tberechnet wird - Kombiniert man
@property --tmit einer Root-Animation von86400000msLänge, steigt die Zeit in Millisekunden an und lässt sich mit Berechnungen für Eigenschaften wierotateodertranslateverknüpfen - Mit
step()lässt sich die Framerate auf etwa 8fps, 24fps oder 60fps begrenzen, und dank Browser-Optimierungen sind selbst Aktualisierungsraten oberhalb von 60fps meist kein Problem - Mit
min(),mod(),sin(),pow()undround()lassen sich Zeitwerte begrenzen, wiederholen, oszillieren und glätten, sodass benutzerdefinierte Easing-Funktionen und Positionsänderungen ohne Keyframes ausgedrückt werden können css-doodlebietet Kurzschreibweisen wie@t,@Tund@TS, mit denen sich Demos mit vielen mathematischen Ausdrücken kürzer schreiben und schneller ausprobieren lassen
Einen Zeit-Tick als CSS-Variable erzeugen
- Früher war dieser Ansatz nur eingeschränkt einsetzbar, weil sich komplexe mathematische Berechnungen in CSS nur schwer umsetzen ließen
- Heute unterstützt CSS die nötigen mathematischen Funktionen wie
mod(),round()und trigonometrische Funktionen, sodass zeitbasierte Animationen wieder interessant werden - Zum Ansehen der Demos muss möglicherweise Experimental feature flags aktiviert werden
Grundlegende Funktionsweise
- CSS kann wie JavaScript nicht direkt einen Timer starten, aber mit der CSS Houdini API lässt sich die benutzerdefinierte Variable
--tdefinieren, um Zeit in Millisekunden zu verfolgen - Im Grundbeispiel wird
@property --tals<integer>deklariert, in@keyframes tickvon0bis86400000verändert und auf:rooteine Animation86400000ms linear infiniteangewendet --tsteigt jede Millisekunde um1und erreicht nach 1 Sekunde den Wert1000- Mit
counter()lässt sich der aktuelle Wert von--tauf dem Bildschirm anzeigen - Verknüpft man eigenschaftsseitige Berechnungen wie
rotate: calc(var(--t) * .001turn)mit--t, ändern sich die Werte mit der Zeit und ergeben eine Animation
Steuerung der Framerate
- Für flüssige Animationen reicht eine Aktualisierungsrate von 60fps aus
- Durch Browser-Rendering-Optimierungen ist auch eine höhere Aktualisierungsrate als 60fps in der Regel unproblematisch
- Bei Bedarf lässt sich die Framerate mit step() in
animation-timing-functiondirekt begrenzen- 8fps:
step(calc(86400000/(1000/8))) - 24fps:
step(calc(86400000/(1000/24))) - 60fps:
step(calc(86400000/(1000/60)))
- 8fps:
Einen steigenden Zeitwert in Animationswerte umwandeln
- Der Wert von
--tsteigt fortlaufend in eine Richtung an - Ein
angle-Wert darf auch über360deghinausgehen, aber nicht alle CSS-Eigenschaften behandeln Werte zyklisch translate: calc(var(--t) * .001px)verschiebt eine Box immer weiter nach rechts-
Mit min() anhalten
- Soll die Bewegung beim Erreichen eines bestimmten Offsets stoppen, kann min() verwendet werden
translate: min(270px, calc(var(--t) * .5px))begrenzt den Verschiebungswert auf 270px- Für eine exakt steuerbare Dauer kann stattdessen
--tselbst begrenzt werden translate: calc(min(3000, var(--t)) * (270px / 3000))- Diese Berechnung bewegt das Element in 3 Sekunden um 270px
-
Mit mod() wiederholen
- Soll die Box nach der Bewegung nach rechts wieder von vorn beginnen, wird mod() verwendet
- Mit
translate: calc(mod(var(--t)/4, 270) * 1px)lässt sich der Offset wiederholen
-
Mit sin() eine Hin-und-zurück-Bewegung erzeugen
- Eine Hin-und-zurück-Bewegung lässt sich mit sin() ausdrücken
- Dabei wird der Zeitwert mit einem trigonometrischen Ausdruck verknüpft, etwa in
translate: calc(sin(mod(var(--t)/135, 270)) * 135px)
Benutzerdefinierte Easing-Funktionen
- Mit CSS-Mathefunktionen und der Variablen
--tlassen sich benutzerdefinierte Easing-Funktionen erzeugen, die mitcubic-bezier()schwer oder gar nicht erreichbar sind - Zunächst wird
--tauf einen Wert zwischen0und1begrenzt--t01: calc(min(1000, var(--t)) / 1000)verändert sich innerhalb von 1 Sekunde von 0 auf 1
-
ease-out-cubic
ease-out-cubicbasiert auf der Formel1 - pow(1 - t, 3)- Nach dem Erzeugen des Werts
--ease-out-cubicwird er mittranslate: calc(var(--ease-out-cubic) * 270px)verknüpft
-
ease-out-elastic
ease-out-elasticbasiert auf der Formelpow(2, -10t) * sin((10t - .75) * 2/3 * PI) + 1- Auf dieselbe Weise wird
--ease-out-elasticgebildet und für einetranslate-Bewegung über 270px verwendet
Formeln mit css-doodle verkürzen
- Je komplexer die Formeln werden, desto schwerer lesbar machen
var()undcalc()den Code - Die neueste Version von css-doodle ergänzt die @t-Funktion zur Darstellung von
--tund akzeptiert darin direkt einfache mathematische Ausdrücke - Ein
rotate-Ausdruck lässt sich so verkürzen- bisher:
rotate: calc(mod(var(--t) / 1000, 10) * 5deg) - mit
css-doodle:rotate: @t(/1000, %10, *5deg)
- bisher:
- Auch ohne geschriebene Keyframes lassen sich so mathematikbasierte Animationen mit kurzem Code erstellen
- Durch das Ändern von Parametern kann man neue Ergebnisse schnell ausprobieren
-
@T und @TS
- Zusätzlich zu
@tsteht das großgeschriebene @T für die Time Ticks seit Tagesbeginn - @TS ist die Kurzform von
@t(/1000)und verfolgt die Zeit in Sekunden - Ein Uhr-Beispiel ist auf CodePen zu sehen
- Sekundenzeiger:
rotate: @TS(*6, %360deg) - Minutenzeiger:
rotate: @TS(/60, *6, %360deg) - Stundenzeiger:
rotate: @TS(/60, /12, *6, %360deg)
- Zusätzlich zu
-
Sprungbewegung des Sekundenzeigers mit round()
- Soll sich der Sekundenzeiger springend bewegen, kann round() verwendet werden
- Der dritte Parameter von
round()legt das Rundungsintervall fest - Bei einer Uhr entspricht ein Schritt
360 / 60 = 6deg, daher wirdrotate: round(down, @TS(*6, %360deg), 6deg)verwendet
Demos und Anwendungspunkte
- Ein Beispiel, das Farbe und Position gemeinsam animiert, ist auf CodePen zu sehen
- Ein Beispiel für Textfarben-Animationen gibt es auf CodePen
- Keyframes können intuitiver wirken, aber in Demo-Szenen mit vielen mathematischen Berechnungen und Eingabevariablen lässt sich mit dem Ansatz, Zeit als Variable zu verwenden, leichter eine größere Vielfalt an Ergebnissen erzeugen
- Mit dem Update vom 17. Mai 2024 wurde eine Demo für Textfarben-Animationen hinzugefügt
1 Kommentare
Hacker-News-Kommentare
Wenn man den Fortschritt per JS steuern möchte, empfiehlt sich auch ein negativer
animation-delayZum Beispiel startet
animation-delay: -1500msdie Animation sofort, springt aber an die Stelle bei 1,5 SekundenSteuert man diesen Wert per JS, kann man CSS-Animationen aus JS heraus praktisch scrubben und so alle Animationen mit einer Game-Engine-artigen Compute-Update-Render-Tick-Schleife synchronisieren
https://developer.mozilla.org/en-US/docs/Web/CSS/animation-d...
Sobald es auch nur etwas komplexer wird als einfache Easing-Funktionen oder ein, zwei grundlegende Keyframe-Kanäle, stößt dieser Ansatz schnell an seine Grenzen
Ich nutze seit einigen Jahren Theatre.js und bin recht zufrieden damit
Die Bibliothek ist aufgeteilt in eine Studio-UI zum Bearbeiten von Keyframes und Bézierkurven auf einer Timeline sowie eine Runtime, die diese Keyframes relativ zur Timeline interpoliert
Wenn mehrere Animationen aufeinander abgestimmt laufen sollen, ist es einen Blick wert
https://www.theatrejs.com/
Sehr beeindruckend, aber irgendwie fühlt es sich auch so an, als kämen wir erst jetzt in die Nähe dessen, was vor Jahrzehnten mit Flash möglich war
Ich wünsche mir Flash nicht zurück, aber ich hätte gern benutzerfreundlichere Werkzeuge zum Erstellen von CSS-Animationen
CSS unterstützt inzwischen ausreichend mathematische Funktionen wie
mod(),round()und trigonometrische FunktionenCSS kann zwar nicht wie JavaScript einen Timer starten, aber heute kann man mit der CSS Houdini API benutzerdefinierte Variablen definieren, die Zeit im Millisekundenbereich verfolgen
Aber wozu braucht man all das, wenn es JavaScript gibt?
Sollte sich die Darstellungsebene nicht auf Darstellungsprimitive konzentrieren? Warum werden dort immer mehr höherstufige Funktionen eingebaut?
Wenn man in JS für Animationen trigonometrische Funktionen ausführt, manipuliert man vermutlich auch bei jeder Millisekunde das DOM, um die UI zu aktualisieren, und das ist sehr teuer
Das Rendering wird vom DOM angetrieben, das aus HTML besteht oder per JS aktualisiert wird
CSS liegt nur darüber, indem es der Painting-Engine Parameter dafür übergibt, was mit dem DOM zu tun ist, und dabei Standardwerte überschreibt
Man kann DOM-Blöcke hinzufügen, das Positionierungsverhalten mit komplexen Algorithmen verändern, und selbst Margins sind in Wahrheit nicht so simpel
Ich halte es nicht für eine großartige Lösung, aber offenbar begann es damit, dass man HTML-Code nicht aktualisieren musste, nur um beim DOM-Rendering Farben zu ändern
Vermutlich, weil das Aktualisieren von HTML-Code überhaupt keinen Spaß machte; meiner Ansicht nach wurde von Anfang an das falsche Problem gelöst
Allerdings war das eben auch die Zeit der „Webmaster“
Es ist leichter zu lesen, wenn in der Fetch-Logik nicht auch die geometrische Fortschrittslogik eines Loader-DOM-Elements steckt, etwa das Berechnen des Rotationswinkels und das Anwenden von Inline-Styles
Ich weiß, dass es heutzutage nicht beliebt ist, Schokolade und Erdnussbutter getrennt zu halten, aber solche Konventionen ziehen Grenzen, die es leichter machen abzuleiten, wo Code zu finden ist und wo er hingehört
Spaß beiseite: CSS ist deklarativ, und es ist natürlich, dass sich die Probleme, die man mit JS und CSS lösen kann, überschneiden
Ich freue mich wirklich über die neuen Funktionen, die zuletzt zu CSS hinzugekommen sind, und weniger interpretierter Code ist eine gute Sache
Das im Artikel verwendete benutzerdefinierte CSS-
@propertyist großartig, und die Browser-Unterstützung liegt ebenfalls bei 88% [1]Allerdings sollte man beim Setzen eines alternativen
initial-valueauf Unterschiede im Browserverhalten achtenChrome verwendet
initial-value, wenn eine CSS-Variable nicht definiert ist oder auf einen ungültigen Wert gesetzt wurdeFirefox verwendet
initial-valuenur, wenn die Variable nicht definiert istFür die meisten Projekte ist das wohl kein Problem, aber in einem aktuellen Projekt mussten wir den Standardwert in Firefox per JavaScript setzen, um die Unterschiede zwischen den Browser-Implementierungen auszugleichen
[1] https://caniuse.com/?search=%40property
about:configöffnen undlayout.css.properties-and-values.enabledaktivierenEinen in
:root{}definierten Eigenschaftswert abrufen:getComputedStyle(document.documentElement).getPropertyValue("--some-custom-css-var");Eigenschaftswert setzen:
document.documentElement.style.setProperty("--some-custom-css-var", "some value");In Browsern, in denen CSS-
@propertynoch nicht aktiviert ist, kann man das wie einen Polyfill verwendenIm aktuellen Android Chrome funktionieren die Demos vor
mod()interessanterweise auch ohne Experimental feature flags, abermod()undsin()bewirken gar nichtsIch wollte so etwas schon länger, weil ich etwas wie box-breathing bauen wollte
Ich wollte die Phasen Einatmen–Anhalten–Ausatmen–Anhalten jeweils passend zum Körper und zum gewünschten Effekt anpassen können
Ohne einen Ansatz wie auf dieser Seite habe ich keinen Weg gefunden, das ohne JS oder ohne absurde Komplexität umzusetzen
Meine Güte, ein Stylesheet! Die Demos auf dieser Seite sind wirklich kreativ
Der Artikel ist wirklich sehr gut geschrieben
Chrome unterstützt CSS-
mod()noch nicht in einem offiziellen Release, daher werden die meisten Beispiele auf der Seite nicht animiert, sofern man keine Chrome-Preview-Version nutztWenn man keine Tick-Steuerung braucht und nur flüssige CSS-Animationen in allen Browsern möchte, ist die FLIP-Technik nützlich
https://medium.com/outsystems-experts/flip-your-60-fps-anima...
Die Animation am Ende der Seite ist sehr beeindruckend
Sie wirkt eher wie ein Video als wie einzelne Elemente
https://RTCode.io ist ein Live-Web-Playground, der ohne Neuladen und ohne Zurücksetzen des Zustands funktioniert, und kann daher für alle, die mit CSS-Animationen oder Transitions arbeiten, sehr hilfreich sein
Die Demo gibt es auf https://RTCode.io
Um einen Pen zu importieren, muss man den Hostnamen in der URL in
xcodepen.ioändern