Fortgeschrittene CSS-Animationen mit cubic-bezier()

Avatar of Temani Afif
Temani Afif am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Bei komplexen CSS-Animationen gibt es eine Tendenz, umfangreiche @keyframes mit vielen Deklarationen zu erstellen. Es gibt jedoch ein paar Tricks, über die ich sprechen möchte, die Dinge erleichtern können, während wir bei Vanilla CSS bleiben.

  1. Mehrere Animationen
  2. Timing-Funktionen

Der erste Trick ist weiter verbreitet und vertrauter, der zweite ist weniger gebräuchlich. Dafür könnte es gute Gründe geben – das Verketten von Animationen mit Kommas ist relativ einfacher, als die verschiedenen verfügbaren Timing-Funktionen und ihre Wirkungsweise zu verstehen. Es gibt eine besonders clevere Timing-Funktion, die uns die volle Kontrolle gibt, um benutzerdefinierte Timing-Funktionen zu erstellen. Das wäre cubic-bezier(), und in diesem Beitrag zeige ich Ihnen die Leistungsfähigkeit davon und wie sie verwendet werden kann, um schicke Animationen ohne zu viel Komplexität zu erstellen.

Beginnen wir mit einem einfachen Beispiel, das zeigt, wie wir einen Ball in interessanten Richtungen bewegen können, wie eine Unendlichkeitsform (∞).

Wie Sie sehen, gibt es keinen komplexen Code – nur zwei Keyframes und eine cubic-bezier() Funktion. Und doch erhalten wir eine ziemlich komplex aussehende finale Unendlichkeitsform-Animation.

Cool, oder? Tauchen wir ein!

Die cubic-bezier() Funktion

Beginnen wir mit der offiziellen Definition

Eine kubische Bézier-Easing-Funktion ist eine Art von Easing-Funktion, die durch vier reelle Zahlen definiert wird, die die beiden Kontrollpunkte, P1 und P2, einer kubischen Bézier-Kurve spezifizieren, deren Endpunkte P0 und P3 fest bei (0, 0) bzw. (1, 1) liegen. Die x-Koordinaten von P1 und P2 sind auf den Bereich [0, 1] beschränkt.

Die obige Kurve definiert, wie das Ergebnis (y-Achse) in Bezug auf die Zeit (x-Achse) verläuft. Jede Achse hat einen Bereich von [0, 1] (oder [0% 100%]). Wenn wir eine Animation haben, die zwei Sekunden (2s) dauert, dann

0 (0%) = 0s 
1 (100%) = 2s

Wenn wir left von 5px auf 20px animieren wollen, dann

0 (0%) = 5px 
1 (100%) = 20px

X, die Zeit, ist immer auf [0 1] beschränkt; Y, das Ergebnis, kann jedoch über [0 1] hinausgehen.

Mein Ziel ist es, P1 und P2 anzupassen, um die folgenden Kurven zu erstellen

Parabel-Kurve
Sinus-Kurve

Sie denken vielleicht, dass dies unmöglich zu erreichen ist, denn wie in der Definition angegeben, sind P0 und P3 bei (0,0) und (1,1) fixiert, was bedeutet, dass sie nicht auf derselben Achse liegen können. Das ist wahr, und wir werden einige mathematische Tricks verwenden, um sie zu "approximieren".

Parabel-Kurve

Beginnen wir mit der folgenden Definition: cubic-bezier(0,1.5,1,1.5). Das ergibt uns die folgende Kurve

cubic-bezier(0,1.5,1,1.5)

Unser Ziel ist es, (1,1) zu verschieben und es auf (0,1) zu setzen, was technisch nicht möglich ist. Also werden wir versuchen, es vorzutäuschen.

Wir haben bereits gesagt, dass unser Bereich [0 1] (oder [0% 100%]) ist. Stellen wir uns also den Fall vor, wenn 0% sehr nahe an 100% liegt. Wenn wir zum Beispiel top von 20px (0%) auf 20.1px (100%) animieren wollen, dann können wir sagen, dass sowohl der Anfangs- als auch der Endzustand gleich sind.

Hm, aber unser Element wird sich gar nicht bewegen, oder?

Nun, es wird sich ein wenig bewegen, weil der Y-Wert 20.1px (100%) überschreitet. Aber das reicht nicht für eine wahrnehmbare Bewegung.

Aktualisieren wir die Kurve und verwenden stattdessen cubic-bezier(0,4,1,4). Beachten Sie, wie unsere Kurve viel höher ist als zuvor.

cubic-bezier(0,4,1,4)

Aber immer noch keine Bewegung – auch wenn der Top-Wert über 3 (oder 300%) liegt. Versuchen wir cubic-bezier(0,20,1,20).

cubic-bezier(0,20,1,20)

Ja! Es begann sich ein wenig zu bewegen. Haben Sie die Entwicklung der Kurve jedes Mal bemerkt, wenn wir den Wert erhöhen? Sie bringt unseren Punkt (1,1) "visuell" näher an (0,1), wenn wir herauszoomen, um die gesamte Kurve zu sehen, und das ist der Trick.

Durch die Verwendung von cubic-bezier(0,V,1,V), wobei V ein sehr großer Wert ist und sowohl der Anfangs- als auch der Endzustand sehr nahe beieinander liegen (oder fast gleich sind), können wir die Parabel-Kurve simulieren.

Ein Beispiel ist tausend Worte wert.

Ich habe die "magische" Cubic-Bezier-Funktion dort auf die top-Animation angewendet, plus eine lineare, die auf left angewendet wird. Das gibt uns die gewünschte Kurve.

Die Mathematik im Detail

Für diejenigen unter Ihnen, die mathematisch veranlagt sind, können wir diese Erklärung weiter aufschlüsseln. Eine kubische Bézier-Kurve kann mit der folgenden Formel definiert werden:

P = (1−t)³P0 + 3(1−t)²tP1 + 3(1−t)t²P2 + t³P3

Jeder Punkt ist wie folgt definiert: P0 = (0,0), P1 = (0,V), P2 = (1,V) und P3 = (1,1).

Dies ergibt uns die beiden Funktionen für x- und y-Koordinaten:

  • X(t) = 3(1−t)t² + t³ = 3t² - 2t³
  • Y(t) = 3(1−t)²tV +3(1−t)t²V + t³ = t³ - 3Vt² + 3Vt

V ist unser großer Wert und t liegt im Bereich [0 1]. Wenn wir unser vorheriges Beispiel betrachten, gibt uns Y(t) den Wert von top, während X(t) den Fortschritt der Zeit darstellt. Die Punkte (X(t),Y(t)) definieren dann unsere Kurve.

Finden wir den Maximalwert von Y(t). Dazu müssen wir den Wert von t finden, der Y'(t) = 0 ergibt (wenn die Ableitung gleich 0 ist).

Y'(t) = 3t² - 6Vt + 3V

Y'(t) = 0 ist eine quadratische Gleichung. Ich werde den langweiligen Teil überspringen und Ihnen das Ergebnis geben, das lautet t = V - sqrt(V² - V).

Wenn V ein großer Wert ist, ist t gleich 0.5. Also ist Y(0.5) = Max und X(0.5) ist gleich 0.5. Das bedeutet, wir erreichen den Maximalwert auf halbem Weg der Animation, was der gewünschten Parabel-Kurve entspricht.

Außerdem ergibt Y(0.5) (1 + 6V)/8, und das ermöglicht uns, den Maximalwert basierend auf V zu finden. Da wir für V immer einen großen Wert verwenden, können wir vereinfachen zu 6V/8 = 0.75V.

Wir haben im letzten Beispiel V = 500 verwendet, also wäre der Maximalwert dort 375 (oder 37500%) und wir erhalten Folgendes:

  • Anfangszustand (0): top: 200px
  • Endzustand (1): top: 199.5px

Es gibt einen Unterschied von -0.5px zwischen 0 und 1. Nennen wir es den Inkrement. Für 375 (oder 37500%) haben wir eine Gleichung von 375*-0.5px = -187.5px. Unser animiertes Element erreicht top: 12.5px (200px - 187.5px) und ergibt die folgende Animation.

top: 200px (at 0% of the time ) → top: 12.5px (at 50% of the time) → top: 199.5px (at 100% of the time) 

Oder anders ausgedrückt:

top: 200px (at 0%) → top: 12.5px (at 50%) → top: 200px (at 100%)

Machen wir die umgekehrte Logik. Welchen Wert von V sollten wir verwenden, um unser Element top: 0px erreichen zu lassen? Die Animation ist 200px → 0px → 199.5px, also brauchen wir -200px, um 0px zu erreichen. Unser Inkrement ist immer gleich -0.5px. Der Maximalwert ist gleich 200/0.5 = 400, also 0.75V = 400, was bedeutet V = 533.33.

Unser Element berührt die Oberseite!

Hier ist eine Grafik, die diese gerade durchgeführte Mathematik zusammenfasst.

Parabel-Kurve mit cubic-bezier(0,V,1,V)

Sinus-Kurve

Wir werden fast den gleichen Trick verwenden, um eine Sinus-Kurve zu erstellen, aber mit einer anderen Formel. Diesmal verwenden wir cubic-bezier(0.5,V,0.5,-V).

Wie zuvor, sehen wir uns an, wie sich die Kurve entwickelt, wenn wir den Wert erhöhen.

Three graphs from left to right, showing how the sinusoidal curve gets narrower as the V value increases.

Ich denke, Sie haben die Idee jetzt wahrscheinlich verstanden. Die Verwendung eines großen Wertes für V bringt uns nahe an eine Sinus-Kurve.

Hier ist eine weitere mit einer kontinuierlichen Animation – eine echte Sinus-Animation!

Die Mathematik

Lassen Sie uns in die Mathematik für diese einsteigen! Gemäß derselben Formel wie zuvor erhalten wir die folgenden Funktionen:

  • X(t) = 3/2(1−t)²t + 3/2(1−t)t² + t³ = (3/2)t - (3/2)t² + t³
  • Y(t) = 3(1−t)²tV - 3(1−t)t²V + t³ = (6V + 1)t³ - 9Vt² + 3Vt

Diesmal müssen wir die Minimal- und Maximalwerte für Y(t) finden. Y'(t) = 0 ergibt zwei Lösungen. Nach der Lösung erhalten wir

Y'(t) = 3(6V + 1)t² - 18Vt + 3V = 0

…erhalten wir

  • t' = (3V + sqrt(3V² - V))/(6V + 1)
  • t''= (3V - sqrt(3V² - V))/(6V + 1)

Für einen großen Wert von V haben wir t'=0.211 und t"=0.789. Das bedeutet, dass Y(0.211) = Max und Y(0.789) = Min. Das bedeutet auch, dass X(0.211) = 0.26 und X(0.789) = 0.74. Mit anderen Worten, wir erreichen das Maximum nach 26% der Zeit und das Minimum nach 74% der Zeit.

Y(0.211) ist gleich 0.289V und Y(0.789) gleich -0.289V. Diese Werte haben wir mit einigen Rundungen erhalten, da V sehr groß ist.

Unsere Sinus-Kurve sollte auch die x-Achse (oder Y(t) = 0) zur Hälfte der Zeit (oder X(t) = 0.5) schneiden. Um dies zu beweisen, verwenden wir die zweite Ableitung von Y(t) – die gleich 0 sein sollte –, also Y''(t) = 0.

Y''(t) = 6(6V + 1)t - 18V = 0

Die Lösung ist 3V/(6V + 1), und für einen großen V-Wert ist die Lösung 0.5. Das ergibt Y(0.5) = 0 und X(0.5) = 0.5, was bestätigt, dass unsere Kurve den Punkt (0.5,0) schneidet.

Betrachten wir nun das vorherige Beispiel und versuchen wir, den Wert von V zu finden, der uns wieder zu top: 0% bringt. Wir haben

  • Anfangszustand (0): top: 50%
  • Endzustand (1): top: 49.9%
  • Inkrement: -0.1%

Wir brauchen -50%, um top: 0% zu erreichen, also 0.289V*-0.1% = -50%, was V = 1730.10 ergibt.

Wie Sie sehen, berührt unser Element die Oberseite und verschwindet am unteren Rand, da wir die folgende Animation haben.

top: 50% → top: 0% → top: 50% → top: 100% → top: 50% → and so on ... 

Eine Grafik zur Zusammenfassung der Berechnung.

Sinus-Kurve mit cubic-bezier(0.5,V,0.5,-V)

Und ein Beispiel, das alle Kurven zusammen illustriert.

Ja, Sie sehen vier Kurven! Wenn Sie genau hinschauen, werden Sie feststellen, dass ich zwei verschiedene Animationen verwende, eine, die zu 49.9% geht (ein Inkrement von -0.01%) und eine andere, die zu 50.1% geht (ein Inkrement von +0.01%). Durch Ändern des Vorzeichens des Inkrements steuern wir die Richtung der Kurve. Wir können auch die anderen Parameter der kubischen Bézier-Kurve (nicht die V, die ein großer Wert bleiben sollte) ändern, um weitere Variationen aus denselben Kurven zu erstellen.

Und unten eine interaktive Demo.

Zurück zu unserem Beispiel

Kehren wir zu unserem ursprünglichen Beispiel eines Balls zurück, der sich in Form eines Unendlichkeitssymbols bewegt. Ich habe einfach zwei Sinus-Animationen kombiniert, um es funktionieren zu lassen.

Wenn wir das, was wir zuvor getan haben, mit dem Konzept mehrerer Animationen kombinieren, können wir erstaunliche Ergebnisse erzielen. Hier ist noch einmal das ursprüngliche Beispiel, diesmal als interaktive Demo. Ändern Sie die Werte und sehen Sie den Zauber.

Gehen wir weiter und fügen ein wenig CSS Houdini hinzu. Wir können eine komplexe Transform-Deklaration dank @property animieren (aber CSS Houdini wird derzeit nur in Chrome und Edge unterstützt).

Welche Art von Zeichnungen können Sie damit erstellen? Hier sind einige, die ich erstellen konnte.

Und hier ist eine Spirograph-Animation.

Und eine Version ohne CSS Houdini.

Aus diesen Beispielen lassen sich einige Dinge ableiten:

  • Jeder Keyframe wird mit nur einer Deklaration definiert, die den Inkrement enthält.
  • Die Position des Elements und die Animation sind unabhängig. Wir können das Element problemlos überall platzieren, ohne die Animation anpassen zu müssen.
  • Wir haben keine Berechnungen durchgeführt. Es gibt nicht tonnenweise Winkel oder Pixelwerte. Wir benötigen nur einen winzigen Wert innerhalb des Keyframes und einen großen Wert innerhalb der cubic-bezier() Funktion.
  • Die gesamte Animation kann allein durch Anpassung des Dauerwerts gesteuert werden.

Was ist mit Übergängen?

Die gleiche Technik kann auch mit der CSS-Eigenschaft transition verwendet werden, da sie der gleichen Logik in Bezug auf Timing-Funktionen folgt. Das ist großartig, denn wir können Keyframes vermeiden, wenn wir einige komplexe Hover-Effekte erstellen.

Hier ist, was ich ohne Keyframes gemacht habe.

Mario springt dank der Parabel-Kurve. Wir brauchten überhaupt keine Keyframes, um diese Shake-Animation beim Hover zu erstellen. Die Sinus-Kurve ist perfekt in der Lage, die gesamte Arbeit zu leisten.

Hier ist eine weitere Version von Mario, diesmal mit CSS Houdini. Und ja, er springt immer noch dank der Parabel-Kurve.

Zur Sicherheit hier noch weitere schicke Hover-Effekte ohne Keyframes (wiederum nur Chrome und Edge).

Das ist alles!

Jetzt haben Sie einige magische cubic-bezier() Kurven und die dahinterliegende Mathematik. Der Vorteil ist natürlich, dass benutzerdefinierte Timing-Funktionen wie diese uns schicke Animationen ermöglichen, ohne die komplexen Keyframes, die wir normalerweise verwenden.

Ich verstehe, dass nicht jeder mathematisch veranlagt ist, und das ist in Ordnung. Es gibt Werkzeuge, die helfen, wie Matthew Leins Ceaser, mit dem Sie die Kurvenpunkte verschieben können, um das zu bekommen, was Sie brauchen. Und wenn Sie es noch nicht als Lesezeichen haben, ist cubic-bezier.com eine weitere Option. Wenn Sie mit kubischen Bézier-Kurven außerhalb der CSS-Welt experimentieren möchten, empfehle ich desmos, wo Sie einige mathematische Formeln sehen können.

Unabhängig davon, wie Sie Ihre cubic-bezier() Werte erhalten, hoffe ich, dass Sie jetzt ein Gefühl für ihre Kräfte haben und wie sie zu einem schöneren Code im Prozess beitragen können.