Reine CSS Bézierkurven-Bewegungsbahnen

Avatar of Lu Wang
Lu Wang am

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

Sind Sie ein Liebhaber von Bézierkurven wie ich?

Neben ihrer Eleganz haben Bézierkurven dank ihrer Definition und Konstruktion schöne mathematische Eigenschaften. Kein Wunder, dass sie in so vielen Bereichen weit verbreitet sind.

  • Als Zeichen-/Designwerkzeug: Sie werden in Vektorgrafikprogrammen oft als „Pfade“ bezeichnet.
  • Als Format zur Darstellung von Kurven: Sie werden in SVG, Schriftarten und vielen anderen Vektorgrafikformaten verwendet.
  • Als mathematische Funktion: Wird oft zur Steuerung von Animationszeitpunkten verwendet.

Nun, wie wäre es, Bézierkurven als Bewegungsbahnen mit CSS zu verwenden?

Kurze Wiederholung

Je nach Kontext gehen wir bei der Bezeichnung „Bézierkurve“ oft von einer kubischen Bézierkurve im 2D-Raum aus.

Eine solche Kurve wird durch vier Punkte definiert.

Bezier curve
MarianSigler, Public domain, via Wikimedia Commons

Hinweis: In diesem Artikel bezeichnen wir im Allgemeinen P0 und P3 als Endpunkte, P1 und P2 als Kontrollpunkte.

Das Wort „kubisch“ bedeutet, dass die zugrunde liegende Funktion der Kurve ein kubisches Polynom ist. Es gibt auch „quadratische“ Bézierkurven, die ähnlich sind, aber einen Kontrollpunkt weniger haben.

Das Problem

Nehmen wir an, Sie haben eine beliebige kubische Bézierkurve im 2D-Raum gegeben, wie würden Sie ein Element mit reiner CSS-Animation animieren, sodass es sich *präzise* entlang der Kurve bewegt?

Wie würden Sie beispielsweise diese Animation nachbilden?

In diesem Artikel werden wir drei Methoden mit unterschiedlichen Ansätzen untersuchen. Für jede Lösung präsentieren wir eine interaktive Demo und erklären dann, wie sie funktioniert. Es gibt viele mathematische Berechnungen und Beweise im Hintergrund, aber keine Sorge, wir werden nicht sehr tief darauf eingehen.

Legen wir los!

Methode 1: Zeitverzerrung (Time Warp)

Hier ist die Grundidee:

  • Richten Sie @keyframes ein, um das Element vom einen Endpunkt der Kurve zum anderen zu bewegen.
  • Verzerren Sie die Zeit für jede Koordinate einzeln mit animation-timing-function.

Hinweis: Es gibt viele Beispiele und Erklärungen in Temani Afifs Artikel (2021).

Mit der Funktion cubic-bezier() mit den richtigen Parametern können wir eine Bewegungsbahn für jede kubische Bézierkurve erstellen.

Diese Demo zeigt eine reine CSS-Animation. Dennoch werden Canvas und JavaScript verwendet, die zwei Zwecke erfüllen:

  • Visualisieren Sie die zugrunde liegende Bézierkurve (rote Kurve).
  • Ermöglichen Sie die Anpassung der Kurve mit der typischen „Pfad“-Benutzeroberfläche.

Sie können die beiden Endpunkte (schwarze Punkte) und die beiden Kontrollpunkte (schwarze Quadrate) ziehen. Der JavaScript-Code aktualisiert die Animation entsprechend, indem einige CSS-Variablen aktualisiert werden.

Hinweis: Hier ist eine reine CSS-Version zur Referenz.

Wie es funktioniert

Angenommen, die gewünschte kubische Bézierkurve wird durch vier Punkte definiert: p0, p1, p2 und p3. Wir richten die folgenden CSS-Regeln ein:

/* pseudo CSS code */
div {
  animation-name: move-x, move-y;
  /*
    Define:
    f(x, a, b) = (x - a) / (b - a)
    qx1 = f(p1.x, p0.x, p3.x)
    qx2 = f(p2.x, p0.x, p3.x)
    qy1 = f(p1.y, p0.y, p3.y)
    qy2 = f(p2.y, p0.y, p3.y)
  */
  animation-timing-function: 
    cubic-bezier(1/3, qx1, 2/3, qx1),
    cubic-bezier(1/3, qy1, 2/3, qy2);
}

@keyframes move-x {
  from {
    left: p0.x;
  }
  to {
    left: p3.x;
  }
}

@keyframes move-y {
  from {
    top: p0.y;
  }
  to {
    top: p3.y;
  }
}

Die @keyframes-Regeln move-x und move-y bestimmen die Start- und Endpositionen des Elements. In animation-timing-function haben wir zwei magische cubic-bezier()-Funktionen, deren Parameter so berechnet sind, dass sowohl top als auch left zu jedem Zeitpunkt die richtigen Werte haben.

Ich überspringe die Mathematik, aber ich habe hier einen kurzen Beweis entworfen, für Ihre neugierigen mathematischen Köpfe.

Diskussionen

Diese Methode sollte in den meisten Fällen gut funktionieren. Sie können sogar eine kubische Bézierkurve im 3D-Raum erstellen, indem Sie eine weitere Animation für den z-Wert einführen.

Es gibt jedoch ein paar kleine Einschränkungen:

  • Sie funktioniert nicht, wenn beide Endpunkte auf einer horizontalen oder vertikalen Linie liegen, aufgrund eines Fehlers durch Division durch Null.

Hinweis: In der Praxis können Sie als Workaround einfach einen winzigen Offset hinzufügen.

  • Sie unterstützt keine Bézierkurven mit einer Ordnung höher als 3.
  • Die Optionen für die Animationstimmung sind begrenzt.
    • Wir verwenden 1/3 und 2/3 oben, um eine lineare Zeitgebung zu erreichen.
    • Sie können beide Werte anpassen, um die Zeitgebung zu ändern, aber sie ist im Vergleich zu anderen Methoden begrenzt. Mehr dazu später.

Methode 2: Konkurrierende Animationen

Als Aufwärmübung stellen Sie sich ein Element mit zwei Animationen vor:

div {
  animation-name: move1, move2;
}

Wie lautet der Bewegungsablauf des Elements, wenn die Animationen wie folgt definiert sind?

@keyframes move1 {
  to {
    left: 256px;
  }
}

@keyframes move2 {
  to {
    top: 256px;
  }
}

Wie Sie vielleicht erraten haben, bewegt es sich diagonal.

Nun, was passiert, wenn die Animationen stattdessen so definiert sind?

@keyframes move1 {
  to {
    transform: translateX(256px);
  }
}

@keyframes move2 {
  to {
    transform: translateY(256px);
  }
}

„Aha, Sie können mich nicht täuschen!“, könnten Sie sagen, da Sie bemerken, dass beide Animationen dieselbe Eigenschaft ändern, „move2 muss move1 überschreiben, indem es so aussieht:“

Nun, das dachte ich früher auch. Aber tatsächlich erhalten wir dies:

Der Trick ist, dass move2 keinen from-Frame hat, was bedeutet, dass die Startposition von move1 animiert wird.

In der folgenden Demo wird die Startposition von move2 als sich bewegender blauer Punkt visualisiert.

Quadratische Bézierkurven

Die obige Demo ähnelt der Konstruktion einer quadratischen Bézierkurve.

Bézier 2 big
Phil Tregoning, Public domain, via Wikimedia Commons

Aber sie sehen unterschiedlich aus. Die Konstruktion hat drei sich linear bewegende Punkte (zwei grüne, einen schwarzen), aber unsere Demo hat nur zwei (den blauen Punkt und das Zielobjekt).

Tatsächlich ist der Bewegungsablauf in der Demo eine quadratische Bézierkurve. Wir müssen nur die Keyframes sorgfältig abstimmen. Ich überspringe die Mathematik und verrate nur das Geheimnis.

Angenommen, eine quadratische Bézierkurve wird durch die Punkte p0, p1 und p2 definiert. Um ein Element entlang der Kurve zu bewegen, gehen wir wie folgt vor:

/* pseudo-CSS code */
div {
  animation-name: move1, move2;
}

@keyframes move1 {
  from {
    transform: translate3d(p0.x, p0.y, p0.z);
  }
  /* define q1 = (2 * p1 - p2) */
  to {
    transform: translate3d(q1.x, q1.y, q1.z);
  }
}

@keyframes move2 {
  to {
    transform: translate3d(p2.x, p2.y, p2.z);
  }
}

Ähnlich wie in der Demo von **Methode 1** können Sie die Kurve anzeigen oder anpassen. Zusätzlich zeigt die Demo noch zwei weitere Informationen an:

  • Die mathematische Konstruktion (graue, sich bewegende Teile)
  • Die CSS-Animationen (blaue Teile)

Beide können über die Kontrollkästchen ein- und ausgeblendet werden.

Kubische Bézierkurven

Diese Methode funktioniert auch für kubische Bézierkurven. Wenn die Kurve durch die Punkte p0, p1, p2 und p3 definiert ist, sollten die Animationen wie folgt definiert werden:

/* pseudo-CSS code */
div {
  animation-name: move1, move2, move3;
}

@keyframes move1 {
  from {
    transform: translate3d(p0.x, p0.y, p0.z);
  }
  /* define q1 = (3 * p1 - 3 * p2 + p3) */
  to {
    transform: translate3d(q1.x, q1.y, q1.z);
  }
}

@keyframes move2 {
  /* define q2 = (3 * p2 - 2 * p3) */
  to {
    transform: translate3d(q2.x, q2.y, q2.z);
  }
}

@keyframes move3 {
  to {
    transform: translate3d(p3.x, p3.y, p3.z);
  }
}

Erweiterungen

Was ist mit 3D-Bézierkurven? Tatsächlich waren alle vorherigen Beispiele 3D-Kurven, wir haben uns nur nie um die z-Werte gekümmert.

Was ist mit Bézierkurven höherer Ordnung? Ich bin zu 90 % sicher, dass die Methode natürlich auf höhere Ordnungen erweitert werden kann. Bitte lassen Sie mich wissen, wenn Sie die Formel für Bézierkurven vierter Ordnung oder noch besser, eine generische Formel für Bézierkurven der Ordnung N ausgearbeitet haben.

Methode 3: Standard-Bézierkurvenkonstruktion

Die mathematische Konstruktion von Bézierkurven gibt uns bereits einen guten Hinweis.

Bézier 3 big
Phil Tregoning, Public domain, via Wikimedia Commons

Schritt für Schritt können wir die Koordinaten aller sich bewegenden Punkte bestimmen. Zuerst bestimmen wir die Position des grünen Punkts, der sich zwischen p0 und p1 bewegt.

@keyframes green0 {
  from {
    --green0x: var(--p0x);
    --green0y: var(--p0y);
  }
  to {
    --green0x: var(--p1x);
    --green0y: var(--p1y);
  }
}

Zusätzliche grüne Punkte können auf ähnliche Weise konstruiert werden.

Als Nächstes können wir die Position eines blauen Punkts wie folgt bestimmen:

@keyframes blue0 {
  from {
    --blue0x: var(--green0x);
    --blue0y: var(--green0y);
  }
  to {
    --blue0x: var(--green1x);
    --blue0y: var(--green1y);
  }
}

Wiederholen und wiederholen, schließlich erhalten wir die gewünschte Kurve.

Ähnlich wie bei **Methode 2** können wir mit dieser Methode leicht eine 3D-Bézierkurve erstellen. Es ist auch intuitiv, die Methode für Bézierkurven höherer Ordnung zu erweitern.

Der einzige Nachteil ist die Verwendung von @property, das nicht von allen Browsern unterstützt wird.

Animationszeitpunkt

Alle bisherigen Beispiele haben eine „lineare“ Zeitgebung, was ist mit Easing oder anderen Zeitfunktionen?

Hinweis: Mit „linear“ meinen wir, dass sich die Variable t der Kurve linear von 0 auf 1 ändert. Mit anderen Worten, t ist dasselbe wie der Animationsfortschritt.

animation-timing-function wird in **Methode 2** und **Methode 3** nie verwendet. Wie bei anderen CSS-Animationen können wir hier jede unterstützte Zeitfunktion verwenden, aber wir müssen dieselbe Funktion für alle Animationen (move1, move2 und move3) gleichzeitig anwenden.

Hier ist ein Beispiel für animation-timing-function: cubic-bezier(1, 0.1, 0, 0.9).

Und hier sieht es mit animation-timing-function: steps(18, end) aus.

Andererseits ist **Methode 1** kniffliger, da sie bereits eine cubic-bezier(u1, v1, u2, v2)-Zeitfunktion verwendet. In den obigen Beispielen haben wir u1=1/3 und u2=2/3. Tatsächlich können wir die Zeitgebung durch Ändern beider Parameter anpassen. Wiederum müssen alle Animationen (z. B. move-x und move-y) die gleichen Werte für u1 und u2 haben.

So sieht es aus, wenn u1=1 und u2=0.

Mit **Methode 2** können wir exakt den gleichen Effekt erzielen, indem wir animation-timing-function auf cubic-bezier(1, 0.333, 0, 0.667) setzen.

Tatsächlich funktioniert es allgemeiner:

Angenommen, wir erhalten eine kubische Bézierkurve, und wir haben mit **Methode 1** und **Methode 2** jeweils zwei Animationen für die Kurve erstellt. Für alle gültigen Werte von u1 und u2 haben die folgenden beiden Setups die gleiche Animationszeitgebung:

  • **Methode 1** mit animation-timing-function: cubic-bezier(u1, *, u2, *).
  • **Methode 2** mit animation-timing-function: cubic-bezier(u1, 1/3, u2, 2/3).

Jetzt sehen wir, warum **Methode 1** „begrenzt“ ist: Mit **Methode 1** können wir nur cubic-bezier() mit zwei Parametern verwenden, aber mit **Methode 2** und **Methode 3** können wir jede CSS animation-timing-function verwenden.

Schlussfolgerungen

In diesem Artikel haben wir 3 verschiedene Methoden diskutiert, um Elemente präzise entlang einer Bézierkurve nur mit CSS-Animationen zu bewegen.

Während alle 3 Methoden mehr oder weniger praktikabel sind, haben sie ihre eigenen Vor- und Nachteile:

  • **Methode 1** ist für diejenigen, die mit dem Timing-Funktions-Hack vertraut sind, vielleicht intuitiver. Aber sie ist weniger flexibel bei der Animationszeitgebung.
  • **Methode 2** hat sehr einfache CSS-Regeln. Jede CSS-Zeitfunktion kann direkt angewendet werden. Es kann jedoch schwierig sein, sich die Formeln zu merken.
  • **Methode 3** ergibt mehr Sinn für diejenigen, die mit der mathematischen Konstruktion von Bézierkurven vertraut sind. Die Animationszeitgebung ist ebenfalls flexibel. Andererseits werden nicht alle modernen Browser unterstützt, da @property verwendet wird.

Das war's! Ich hoffe, Sie finden diesen Artikel interessant. Lassen Sie mich Ihre Gedanken wissen!