1 Element CSS Regenbogen-Farbverlauf Unendlichkeit

Avatar of Ana Tudor
Ana Tudor am

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

Die Idee dazu kam mir, als ich dieses Farbverlauf-Unendlichkeitslogo von Infographic Paradise sah.

Original illustration. Shows a thick infinity symbol with a rainbow gradient filling its two loops and some highlights over this gradient.
Der ursprüngliche Farbverlauf-Unendlichkeit.

Nach vier Stunden und etwa zwanzig Minuten, von denen über vier Stunden für die Feinabstimmung von Positionierung, Kanten und Lichtern aufgewendet wurden… hatte ich schließlich das folgende Ergebnis:

Screenshot of my version. Shows a thick infinity symbol with a rainbow gradient filling its two loops and some highlights over this gradient.
Meine Version der Regenbogen-Farbverlauf-Unendlichkeit.

Der Farbverlauf sieht nicht wie in der Originalillustration aus, da ich mich entschieden habe, den Regenbogen logisch zu generieren, anstatt den Dev Tools Picker oder so etwas zu verwenden. Aber ansonsten denke ich, dass ich ziemlich nah dran bin – also lasst uns sehen, wie ich es gemacht habe!

Markup

Wie Sie wahrscheinlich schon am Titel erraten haben, besteht das HTML nur aus einem Element.

<div class='∞'></div>

Styling

Entscheidung für den Ansatz

Der erste Gedanke, der einem beim Anblick des obigen Beispiels in den Sinn kommen könnte, ist die Verwendung von konischen Farbverläufen als Randbilder. Leider spielen border-image und border-radius nicht gut zusammen, wie die interaktive Demo unten zeigt.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Immer wenn wir ein border-image setzen, wird border-radius einfach ignoriert, sodass die gemeinsame Verwendung von beiden leider keine Option ist.

Der hier verwendete Ansatz ist also die Verwendung von conic-gradient() Hintergründen, um dann den mittleren Teil mithilfe einer mask zu entfernen. Schauen wir uns an, wie das funktioniert!

Erstellen der beiden ∞ Hälften

Wir legen zuerst einen Außendurchmesser fest.

$do: 12.5em;

Wir erstellen die beiden Hälften des Unendlichkeitszeichens mithilfe der Pseudo-Elemente ::before und ::after unseres .∞ Elements. Um diese beiden Pseudo-Elemente nebeneinander zu platzieren, verwenden wir ein Flex-Layout für ihr übergeordnetes Element (das Unendlichkeits-Element .∞). Jedes davon hat sowohl width als auch height gleich dem Außendurchmesser $do. Wir runden sie außerdem mit einem border-radius von 50% ab und geben ihnen einen Dummy-background, damit wir sie sehen können.

.∞ {
  display: flex;
	
  &:before, &:after {
    width: $do; height: $do;
    border-radius: 50%;
    background: #000;
    content: '';
  }
}

Wir haben das .∞ Element auch vertikal und horizontal in der Mitte seines übergeordneten Elements (in diesem Fall des body) platziert, indem wir den Flexbox-Ansatz verwenden.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Wie conic-gradient() funktioniert

Um die conic-gradient() Hintergründe für die beiden Hälften zu erstellen, müssen wir zuerst verstehen, wie die conic-gradient() Funktion funktioniert.

Wenn innerhalb der conic-gradient() Funktion eine Liste von Stopps ohne explizite Positionen steht, wird der erste als bei 0% (oder 0deg, dasselbe) und der letzte als bei 100% (oder 360deg) betrachtet, während alle übrigen gleichmäßig im Intervall [0%, 100%] verteilt werden.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Wenn wir nur 2 Stopps haben, ist es einfach. Der erste ist bei 0%, der zweite (und letzte) bei 100% und dazwischen liegen keine weiteren Stopps.

Wenn wir 3 Stopps haben, ist der erste bei 0%, der letzte (dritte) bei 100%, während der zweite genau in der Mitte des Intervalls [0%, 100%], also bei 50% liegt.

Wenn wir 4 Stopps haben, ist der erste bei 0%, der letzte (vierte) bei 100%, während der zweite und dritte das Intervall [0%, 100%] in 3 gleiche Intervalle teilen und bei 33.(3)% bzw. 66.(6)% positioniert sind.

Wenn wir 5 Stopps haben, ist der erste bei 0%, der letzte (fünfte) bei 100%, während der zweite, dritte und vierte das Intervall [0%, 100%] in 4 gleiche Intervalle teilen und bei 25%, 50% und 75% positioniert sind.

Wenn wir 6 Stopps haben, ist der erste bei 0%, der letzte (sechste) bei 100%, während der zweite, dritte, vierte und fünfte das Intervall [0%, 100%] in 5 gleiche Intervalle teilen und bei 20%, 40%, 60% und 80% positioniert sind.

Im Allgemeinen, wenn wir n Stopps haben, ist der erste bei 0%, der letzte bei 100%, während die dazwischen liegenden das Intervall [0%, 100%] in n-1 gleiche Intervalle teilen, die jeweils 100%/(n-1) umfassen. Wenn wir den Stopps 0-basierte Indizes geben, dann ist jeder von ihnen bei i*100%/(n-1) positioniert.

Für den ersten ist i gleich 0, was uns 0*100%/(n-1) = 0% ergibt.

Für den letzten (n-ten) ist i gleich n-1, was uns (n-1)*100%/(n-1) = 100% ergibt.

Hier entscheiden wir uns für 9 Stopps, was bedeutet, dass wir das Intervall [0%, 100%] in 8 gleiche Intervalle aufteilen.

In Ordnung, aber wie kommen wir an die Stopp-Liste?

Die HSL()-Stopps

Nun, zur Einfachheit wählen wir, sie als Liste von HSL-Werten zu generieren. Wir halten die Sättigung und die Helligkeit konstant und variieren den Farbton. Der Farbton ist ein Winkelwert, der von 0 bis 360 reicht, wie wir hier sehen können.

Hue scale from 0 to 360 in the HSB/HSL models.
Visuelle Darstellung der Farbtonskala von 0 bis 360 (Sättigung und Helligkeit bleiben konstant).

Vor diesem Hintergrund können wir eine Liste von hsl() Stopps mit fester Sättigung und Helligkeit sowie variablem Farbton erstellen, wenn wir den Start-Farbton $hue-start, den Farbtonbereich $hue-range (dies ist der End-Farbton minus der Start-Farbton) und die Anzahl der Stopps $num-stops kennen.

Nehmen wir an, wir halten die Sättigung und die Helligkeit bei 85% bzw. 57% fest (willkürliche Werte, die wahrscheinlich für bessere Ergebnisse angepasst werden können) und gehen zum Beispiel von einem Start-Farbton von 240 zu einem End-Farbton von 300 und verwenden 4 Stopps.

Um diese Liste von Stopps zu generieren, verwenden wir eine get-stops() Funktion, die diese drei Dinge als Argumente nimmt.

@function get-stops($hue-start, $hue-range, $num-stops) {}

Wir erstellen die Liste der Stopps $list, die ursprünglich leer ist (und die wir am Ende zurückgeben werden, nachdem wir sie gefüllt haben). Wir berechnen auch die Spanne eines der gleichen Intervalle, in die unsere Stopps das volle Intervall von Start bis Ende aufteilen ($unit).

@function get-stops($hue-start, $hue-range, $num-stops) {
  $list: ();
  $unit: $hue-range/($num-stops - 1);
	
  /* populate the list of stops $list */
	
  @return $list
}

Um unser $list zu füllen, durchlaufen wir die Stopps, berechnen den aktuellen Farbton, verwenden den aktuellen Farbton, um den hsl() Wert an diesem Stopp zu generieren, und fügen ihn dann der Liste der Stopps hinzu.

@for $i from 0 to $num-stops {
  $hue-curr: $hue-start + $i*$unit;
  $list: $list, hsl($hue-curr, 85%, 57%);
}

Wir können die Stopp-Liste, die diese Funktion zurückgibt, nun für jede Art von Farbverlauf verwenden, wie aus den Nutzungsbeispielen für diese Funktion in der interaktiven Demo unten ersichtlich ist (die Navigation funktioniert sowohl über die Vorwärts-/Rückwärts-Pfeile an den Seiten als auch über die Pfeiltasten und die Tasten Bildlauf nach unten / Bildlauf nach oben).

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Beachten Sie, dass, wenn unser Bereich ein Ende des Intervalls [0, 360] überschreitet, er vom anderen Ende fortgesetzt wird. Wenn zum Beispiel der Start-Farbton 30 und der Bereich -210 ist (das vierte Beispiel), können wir nur bis 0 gehen, und dann gehen wir weiter von 360 abwärts.

Konische Farbverläufe für unsere beiden Hälften

In Ordnung, aber wie bestimmen wir $hue-start und $hue-range für unseren speziellen Fall?

Im Originalbild ziehen wir eine Linie zwischen den Mittelpunkten der beiden Hälften der Schleife und sehen, beginnend mit dieser Linie, im Uhrzeigersinn in beiden Fällen, wo wir starten und wo wir im Farbton-Intervall [0, 360] enden und welche anderen Farbtöne wir passieren.

Original illustration, annotated. We've marked out the central points of the two halves, connected them with a line and used this line as the start for going around each of the two halves in the clockwise direction.
Wir starten von der Linie, die die Mittelpunkte der beiden Hälften verbindet, und bewegen uns im Uhrzeigersinn um sie herum.

Um die Dinge zu vereinfachen, betrachten wir, dass wir die gesamte Farbtonskala [0, 360] entlang unseres Unendlichkeitszeichens durchlaufen. Das bedeutet, dass der Bereich für jede Hälfte absolut 180 (die Hälfte von 360) beträgt.

Hue scale from 0 to 360 in the HSB/HSL models, with saturation and lightness fixed at 100% and 50% respectively. Red corresponds to a hue of 0/ 360, yellow to a hue of 60, lime to a hue of 120, cyan to a hue of 180, blue to a hue of 240, magenta to a hue of 300.
Entsprechung von Schlüsselwörtern zu Farbtonwerten bei Sättigung und Helligkeit, die auf 100% bzw. 50% festgelegt sind.

Auf der linken Hälfte beginnen wir bei etwas, das wie eine Mischung aus Cyan (Farbton 180) und Hellgrün (Farbton 120) aussieht, also nehmen wir den Start-Farbton als Durchschnitt der Farbtöne dieser beiden (180 + 120)/2 = 150.

Original illustration, annotated. For the left half, our start hue is 150 (something between a kind of cyan and a kind of lime), we pass through yellows, which are around 60 in hue and end up at a kind of red, 180 away from the start, so at 330.
Der Plan für die linke Hälfte.

Wir erreichen eine Art Rot, das 180 vom Startwert entfernt ist, also bei 330, egal ob wir 180 subtrahieren oder addieren.

(150 - 180 + 360)%360 = (150 + 180 + 360)%360 = 330

Also… gehen wir rauf oder runter? Nun, wir durchlaufen Gelbtöne, die auf der Farbtonskala um 60 liegen, also gehen wir von 150 abwärts, nicht aufwärts. Abwärts gehen bedeutet, dass unser Bereich negativ ist (-180).

Original illustration, annotated. For the right half, our start hue is 150 (something between a kind of cyan and a kind of lime), we pass through blues, which are around 240 in hue and end up at a kind of red, 180 away from the start, so at 330.
Der Plan für die rechte Hälfte.

Auf der rechten Hälfte beginnen wir ebenfalls mit demselben Farbton zwischen Cyan und Hellgrün (150) und enden auch mit derselben Art von Rot (330), aber diesmal durchlaufen wir Blautöne, die um 240 liegen, was bedeutet, dass wir von unserem Start-Farbton von 150 aufwärts gehen, sodass unser Bereich in diesem Fall positiv ist (180).

Was die Anzahl der Stopps betrifft, so sollten 9 ausreichen.

Aktualisieren wir nun unseren Code und verwenden die Werte für die linke Hälfte als Standardwerte für unsere Funktion.

@function get-stops($hue-start: 150, $hue-range: -180, $num-stops: 9) {
  /* same as before */
}

.∞ {
  display: flex;
	
  &:before, &:after {
    /* same as before */
    background: conic-gradient(get-stops());
  }
  
  &:after {
    background: conic-gradient(get-stops(150, 180));
  }
}

Und jetzt haben unsere beiden Kreisscheiben conic-gradient() Hintergründe.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Wir möchten jedoch nicht, dass diese konischen Farbverläufe von oben beginnen.

Für die erste Kreisscheibe soll sie von rechts beginnen – das ist 90° von oben in der positiven (Uhrzeigersinn-) Richtung. Für die zweite Kreisscheibe soll sie von links beginnen – das ist 90° von oben in der anderen (negativen) Richtung, was 270° von oben in der Uhrzeigersinn-Richtung entspricht (da negative Winkel aus irgendeinem Grund nicht zu funktionieren scheinen).

The conic gradient for the first (left) half starts from the right, which means an offset of 90° in the clockwise (positive) direction from the top. The conic gradient for the second (right) half starts from the left, which means an offset of 270° in the clockwise (positive) direction (and of 90° in the negative direction) from the top.
Winkelversätze von oben für unsere beiden Hälften.

Ändern wir unseren Code, um dies zu erreichen.

.∞ {
  display: flex;
	
  &:before, &:after {
    /* same as before */
    background: conic-gradient(from 90deg, get-stops());
  }
  
  &:after {
    background: conic-gradient(from 270deg, get-stops(150, 180));
  }
}

Bis jetzt alles gut!

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Von 🥧 zu 🍩

Der nächste Schritt ist, Löcher aus unseren beiden Hälften auszuschneiden. Wir tun dies mit einer mask oder, genauer gesagt, mit einer radial-gradient() Maske. Dies schließt derzeit die Kantenunterstützung aus, aber da dies etwas ist, das sich in Entwicklung befindet, wird es wahrscheinlich irgendwann in naher Zukunft eine browserübergreifende Lösung sein.

Denken Sie daran, dass CSS-Gradientenmasken standardmäßig alpha-Masken sind (und nur Firefox erlaubt derzeit die Änderung über mask-mode), was bedeutet, dass nur der Alphakanal zählt. Das Überlagern der mask über unserem Element lässt jedes Pixel dieses Elements den alpha-Kanal des entsprechenden Pixels der mask verwenden. Wenn das mask-Pixel vollständig transparent ist (sein alpha-Wert ist 0), dann ist es auch das entsprechende Pixel des Elements.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Um die mask zu erstellen, berechnen wir den Außenradius $ro (die Hälfte des Außendurchmessers $do) und den Innenradius $ri (ein Bruchteil des Außenradius $ro).

$ro: .5*$do;
$ri: .52*$ro;
$m: radial-gradient(transparent $ri, red 0);

Wir setzen dann die mask auf unseren beiden Hälften.

.∞ {
  /* same as before */
	
  &:before, &:after {
    /* same as before */
    mask: $m;
  }
}

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Das sieht in Firefox perfekt aus, aber die Kanten von radialen Farbverläufen mit abrupten Übergängen von einem Stopp zum nächsten sehen in Chrome unschön aus, und folglich auch die inneren Kanten unserer Ringe.

Screenshot. Shows a close-up of the inner edge of the right half in Chrome. These inner edges look jagged and ugly in Chrome.
Nahaufnahme des inneren Rands der rechten Hälfte in Chrome.

Die Lösung hier wäre, keinen abrupten Übergang zwischen den Stopps zu haben, sondern ihn über eine kleine Distanz zu verteilen, sagen wir eine halbe Pixel.

$m: radial-gradient(transparent calc(#{$ri} - .5px), red $ri);

Wir haben nun die gezackten Kanten in Chrome beseitigt.

Screenshot. Shows a close-up of the inner edge of the right half in Chrome after spreading out the transition between stops over half a pixel. These inner edges now look blurry and smoother in Chrome.
Nahaufnahme des inneren Rands der rechten Hälfte in Chrome, nachdem der Übergang zwischen den Stopps über eine halbe Pixel verteilt wurde.

Der nächste Schritt ist, die beiden Hälften so zu versetzen, dass sie tatsächlich ein Unendlichkeitszeichen bilden. Die sichtbaren kreisförmigen Streifen haben beide die gleiche Breite, die Differenz zwischen dem Außenradius $ro und dem Innenradius $ri. Das bedeutet, wir müssen jede lateral um die Hälfte dieser Differenz $ri - $ri verschieben.

.∞ {
  /* same as before */
	
  &:before, &:after {
    /* same as before */
    margin: 0 (-.5*($ro - $ri));
  }
}

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Sich überschneidende Hälften

Wir kommen näher, aber wir haben hier immer noch ein sehr großes Problem. Wir möchten nicht, dass der rechte Teil der Schleife vollständig über dem linken liegt. Stattdessen möchten wir, dass die obere Hälfte des rechten Teils über der des linken Teils liegt und die untere Hälfte des linken Teils über der des rechten Teils.

Wie erreichen wir das also?

Wir verfolgen einen ähnlichen Ansatz wie in einem älteren Artikel: Wir verwenden 3D!

Um besser zu verstehen, wie das funktioniert, betrachten wir das folgende Beispiel mit zwei Karten. Wenn wir sie um ihre x-Achsen drehen, befinden sie sich nicht mehr in der Ebene des Bildschirms. Eine positive Drehung bringt die Unterseite nach vorne und schiebt die Oberseite nach hinten. Eine negative Drehung bringt die Oberseite nach vorne und schiebt die Unterseite nach hinten.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Beachten Sie, dass die obige Demo in Edge nicht funktioniert.

Wenn wir also der linken einen positiven Dreh- und der rechten einen negativen Dreh-Effekt geben, erscheint die obere Hälfte der rechten vor der oberen Hälfte der linken und umgekehrt für die unteren Hälften.

Das Hinzufügen von perspective lässt das, was näher an unseren Augen ist, größer erscheinen und das, was weiter weg ist, kleiner, und wir verwenden viel kleinere Winkel. Ohne perspective haben wir die 3D-Ebenenüberschneidung ohne das 3D-Erscheinungsbild.

Beachten Sie, dass beide Hälften im selben 3D-Kontext sein müssen, was durch Setzen von transform-style: preserve-3d auf dem .∞ Element erreicht wird.

.∞ {
  /* same as before */
  transform-style: preserve-3d;
	
  &:before, &:after {
    /* same as before */
    transform: rotatex(1deg);
  }
  
  &:after {
    /* same as before */
    transform: rotatex(-1deg);
  }
}

Und jetzt sind wir fast am Ziel, aber noch nicht ganz.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Feinabstimmung

Wir haben einen kleinen rötlichen Streifen in der Mitte, weil der Farbverlauf endet und die Schnittlinie nicht ganz übereinstimmen.

Screenshot. Shows a close-up of the intersection of the two halves. In theory, the intersection line should match the start/ end line of the conic gradients, but this isn't the case in practice, so we're still seeing a strip of red along it, even though the red side should be behind the plane of the screen and not visible.
Nahaufnahme eines kleinen Problems an der Schnittstelle der beiden Hälften.

Eine ziemlich unschöne, aber effektive Lösung ist, eine 1px Verschiebung vor der Drehung am rechten Teil (dem Pseudo-Element ::after) hinzuzufügen.

.∞:after { transform: translate(1px) rotatex(-1deg) }

Viel besser!

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Das ist aber immer noch nicht perfekt. Da die inneren Kanten unserer beiden Ringe etwas verschwommen sind, sieht der Übergang zwischen ihnen und den scharfen äußeren Kanten etwas seltsam aus. Vielleicht können wir das verbessern.

Screenshot. Shows a close-up of the area around the intersection of the two halves, where the crisp outer edges meet the blurry inner ones, which looks odd.
Nahaufnahme eines Kontinuitätsproblems (scharfe äußere Kanten treffen auf verschwommene innere).

Eine schnelle Lösung wäre hier, eine radial-gradient() Abdeckung auf jede der beiden Hälften zu legen. Diese Abdeckung ist transparentweiß (rgba(#fff, 0)) für den größten Teil des nicht maskierten Bereichs der beiden Hälften und geht zu durchgehend weiß (rgba(#fff, 1)) entlang sowohl ihrer inneren als auch ihrer äußeren Kanten, sodass wir eine schöne Kontinuität haben.

$gc: radial-gradient(#fff $ri, rgba(#fff, 0) calc(#{$ri} + 1px), 
  rgba(#fff, 0) calc(#{$ro} - 1px), #fff calc(#{$ro} - .5px));

.∞ {
  /* same as before */
	
  &:before, &:after {
    /* same as before */
    background: $gc, conic-gradient(from 90deg, get-stops());
  }
  
  &:after {
    /* same as before */
    background: $gc, conic-gradient(from 270deg, get-stops(150, 180));
  }
}

Der Vorteil wird deutlicher, sobald wir der body einen dunklen background hinzufügen.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Jetzt sieht es auch beim Hineinzoomen besser aus.

Screenshot. Shows a close-up of the area around the intersection of the two halves, we don't have the same sharp contrast between inner and outer edges, not even when zooming in.
Kein scharfer Kontrast mehr zwischen inneren und äußeren Kanten.

Das Endergebnis

Schließlich fügen wir einige verschönernde Details hinzu, indem wir mehr subtile radiale Farbverlauf-Lichter über die beiden Hälften legen. Dies war der Teil, der mich am meisten beschäftigte, da er am wenigsten Logik und am meisten Versuch und Irrtum erforderte. Zu diesem Zeitpunkt habe ich einfach das Originalbild unter das .∞ Element gelegt, die beiden Hälften halbtransparent gemacht und angefangen, Farbverläufe hinzuzufügen und sie zu verfeinern, bis sie ziemlich gut den Lichtern entsprachen. Und man sieht, wann ich es leid wurde, weil die Positionsangaben zu groben Annäherungen mit wenigen Dezimalstellen werden.

Eine weitere coole Ergänzung wären Schlagschatten auf dem Ganzen mit einem filter auf dem body. Leider bricht dies den 3D-Schnitt-Effekt in Firefox, was bedeutet, dass wir ihn dort auch nicht hinzufügen können.

@supports not (-moz-transform: scale(2)) {
  filter: drop-shadow(.25em .25em .25em #000) 
          drop-shadow(.25em .25em .5em #000);
}

Wir haben nun das endgültige statische Ergebnis!

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Aufpeppen mit Animation!

Als ich diese Demo zum ersten Mal teilte, wurde ich nach Animationen gefragt. Zuerst dachte ich, das wäre kompliziert, aber dann fiel mir ein, dass es dank Houdini nicht so sein muss!

Wie in meinem früheren Artikel erwähnt, können wir zwischen Stopps animieren, sagen wir von Rot zu Blau. In unserem Fall bleiben die Sättigungs- und Helligkeitskomponenten der hsl() Werte, die zur Erzeugung des Regenbogenverlaufs verwendet werden, konstant, es ändert sich nur der Farbton.

Für jeden einzelnen Stopp geht der Farbton von seinem Anfangswert zu seinem Anfangswert plus 360, durchläuft dabei die gesamte Farbtonskala. Dies entspricht dem Beibehalten des Anfangsfarbtons und dem Variieren eines Offsets. Dieser Offset --off ist die benutzerdefinierte Eigenschaft, die wir animieren.

Leider bedeutet dies, dass die Unterstützung auf Blink-Browser mit aktiviertem Flag Experimental Web Platform features beschränkt ist. Während conic-gradient() ab Chrome 69 nativ ohne das Flag unterstützt wird, gilt dies nicht für Houdini, sodass in diesem speziellen Fall bisher kein wirklicher Vorteil erzielt wurde.

Screenshot showing the Experimental Web Platform features flag being enabled in Chrome.
Das aktivierte Flag „Experimental Web Platform features“ in Chrome.

Dennoch wollen wir sehen, wie wir das alles in Code umsetzen!

Zunächst modifizieren wir die get-stops() Funktion so, dass der aktuelle Farbton zu jedem Zeitpunkt der Anfangsfarbton des aktuellen Stopps $hue-curr plus unser Offset --off ist.

$list: $list, hsl(calc(#{$hue-curr} + var(--off, 0)), 85%, 57%);

Als nächstes registrieren wir diese benutzerdefinierte Eigenschaft.

CSS.registerProperty({
  name: '--off', 
  syntax: '<number>', 
  initialValue: 0, 
  inherits: true
})

Beachten Sie, dass inherits jetzt erforderlich ist, obwohl es in früheren Versionen der Spezifikation optional war.

Und schließlich animieren wir sie auf 360.

.∞ {
  /* same as before */
	
  &:before, &:after {
    /* same as before */
    animation: shift 2s linear infinite;
  }
}

@keyframes shift { to { --off: 360 } }

Das gibt uns unsere animierte Gradienten-Unendlichkeit!

Animiertes ∞ Logo (Live-Demo, nur Blink mit aktiviertem Flag).

Das war's! Ich hoffe, Sie haben diesen Einblick in das genossen, was heutzutage mit CSS möglich ist!