Der Zustand des Änderns von Verläufen mit CSS-Übergängen und Animationen

Avatar of Ana Tudor
Ana Tudor am

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

Im Jahr 2012 kam Internet Explorer 10 heraus und unterstützte unter anderem endlich CSS-Verläufe und darüber hinaus die Möglichkeit, diese nur mit CSS zu animieren! Damals unterstützte kein anderer Browser dies, aber ich war hoffnungsvoll für die Zukunft.

Leider sind sechs Jahre vergangen und in diesem Bereich hat sich nichts geändert. Edge unterstützt das Animieren von Verläufen mit CSS, genau wie IE 10 damals, aber kein anderer Browser hat dies unterstützt. Und während das Animieren von background-size oder background-position oder der opacity oder Rotation eines darüber liegenden Pseudo-Elements uns weit bringen kann, um coole Effekte zu erzielen, sind diese Workarounds immer noch begrenzt.

Es gibt Effekte, die wir nicht reproduzieren können, ohne viele zusätzliche Elemente oder viele zusätzliche Verläufe hinzuzufügen, wie z. B. den unten gezeigten „Jalousien-Effekt“.

Animated GIF showing a recording of the opening and closing blinds effect. When the blinds are closed, we only see a grey background, when the blinds start to open, we start seeing vertical orange strips (the light coming in) that grow horizontally until the blinds are fully open, so we only see an orange background. After that, the blinds start to close, so the vertical orange strips start getting narrower until they're reduced to nothing when the blinds are fully closed and we only see a grey background again. The whole cycle then repeats itself.
Der Jalousien-Effekt (Live-Demo, nur Edge/IE 10+).

In Edge wird der obige Effekt mit einer Keyframe-animation erzielt

html {
  background: linear-gradient(90deg, #f90 0%, #444 0) 50%/ 5em;
  animation: blinds 1s ease-in-out infinite alternate;
}

@keyframes blinds {
  to {
    background-image: linear-gradient(90deg, #f90 100%, #444 0);
  }
}

Wenn das WET erscheint, können wir es mit DRY machen mit einem Hauch von Sass

@function blinds($open: 0) {
  @return linear-gradient(90deg, #f90 $open*100%, #444 0);
}

html {
  background: blinds() 50%/ 5em;
  animation: blinds 1s ease-in-out infinite alternate;
}

@keyframes blinds { to { background-image: blinds(1) } }

Während wir den Code, den wir schreiben und den wir später bearbeiten müssen, viel wartungsfreundlicher gemacht haben, haben wir immer noch Wiederholungen im kompilierten CSS und sind durch die Tatsache eingeschränkt, dass wir nur zwischen Stopps mit der gleichen Einheit animieren können — während das Animieren von 0% bis 100% einwandfrei funktioniert, führt der Versuch, 0 oder 0px anstelle von 0% zu verwenden, dazu, dass keine Animation stattfindet. Ganz zu schweigen davon, dass Chrome und Firefox einfach von Orange zu Grau wechseln, ohne jegliche Positionsanimation!

Glücklicherweise haben wir heutzutage eine noch bessere Option: CSS-Variablen!

CSS-Variablen sind von Haus aus nicht animierbar, obwohl wir transition- (aber keine animation-!) Effekte erzielen können, *wenn die Eigenschaft, für die wir sie verwenden, animierbar ist*. Wenn sie beispielsweise in einer transform-Funktion verwendet werden, können wir die transform-Eigenschaft transitionen.

Betrachten wir das Beispiel einer Box, die verschoben und gequetscht wird, wenn eine Checkbox aktiviert ist. Auf dieser Box setzen wir eine transform, die von einem Faktor --f abhängt, der anfänglich 1 ist

.box {
  /* basic styles like dimensions and background */
  --f: 1;
  transform: translate(calc((1 - var(--f))*100vw)) scalex(var(--f));
}

Wenn die Checkbox :checked ist, ändern wir den Wert der CSS-Variable --f zu .5

:checked ~ .box { --f: .5 }

Das Festlegen einer transition auf die .box lässt sie reibungslos von einem Zustand zum anderen übergehen

.box {
  /* same styles as before */
  transition: transform .3s ease-in;
}

Beachten Sie, dass dies in der aktuellen Version von Edge aufgrund dieses Fehlers nicht wirklich funktioniert.

CSS-Verläufe sind jedoch Hintergrundbilder, die nur in Edge und IE 10+ animierbar sind. Während wir uns also das Leben erleichtern und die Menge an generiertem CSS für Übergänge reduzieren können (wie im folgenden Code zu sehen ist), machen wir in Bezug auf die Erweiterung der Unterstützung immer noch keine Fortschritte.

.blinds {
  background: linear-gradient(90deg, #f90 var(--pos, 0%), #444 0) 50%/ 5em;
  transition: .3s ease-in-out;
    
  :checked ~ & { --pos: 100%; }
}
Animated gif. The blinds opening effect happens on checking an 'open blinds' checkbox, while unchecking it triggers the closing effect.
Jalousien öffnen/schließen beim Aktivieren/Deaktivieren der Checkbox (Live-Demo, nur Edge).

Hier kommt Houdini ins Spiel, das es uns ermöglicht, benutzerdefinierte Eigenschaften zu registrieren und sie dann zu animieren. Derzeit wird dies nur von Blink-Browsern hinter dem Flag Experimental Web Platform features unterstützt, aber es erweitert die Unterstützung immerhin ein wenig über Edge hinaus.

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

Zurück zu unserem Beispiel registrieren wir die benutzerdefinierte Eigenschaft --pos

CSS.registerProperty({
  name: '--pos', 
  syntax: '<length-percentage>', 
  initialValue: '0%', 
  inherits: true
});

Beachten Sie, dass bedeutet, dass es nicht nur Längen- und Prozentwerte, sondern auch calc()-Kombinationen davon akzeptiert. Im Gegensatz dazu akzeptiert | nur Längen- und Prozentwerte, aber keine calc()-Kombinationen davon.

Beachten Sie, dass die explizite Angabe von inherits jetzt obligatorisch ist, auch wenn sie in früheren Versionen der Spezifikation optional war.

Dies macht jedoch in Chrome keinen Unterschied, selbst mit aktiviertem Flag, wahrscheinlich weil bei Übergängen die Eigenschaft, deren Wert von der CSS-Variablen abhängt, und nicht die CSS-Variable selbst übergangt wird. Und da wir im Allgemeinen keine Übergänge zwischen zwei Hintergrundbildern in Chrome durchführen können, schlägt dies ebenfalls fehl.

Es funktioniert in Edge, aber es funktionierte auch in Edge ohne Registrierung der --pos-Variable, da Edge uns erlaubt, generell zwischen Verläufen zu wechseln.

Was funktioniert in Blink-Browsern mit aktiviertem Flag, ist eine animation anstelle einer transition.

html {
  background: linear-gradient(90deg, #f90 var(--pos, 0%), #444 0) 50%/ 5em;
  animation: blinds .85s ease-in-out infinite alternate;
}

@keyframes blinds { to { --pos: 100%; } }

Dies funktioniert jedoch in Edge nicht mehr, da Edge zwar zwischen Gradientenhintergründen animieren kann, dies aber nicht für benutzerdefinierte Eigenschaften tun kann.

Wir müssen also für Edge einen alternativen Ansatz wählen. Hier ist @supports nützlich, da wir nur prüfen müssen, ob eine Eigenschaft mit -ms- Präfix unterstützt wird.

@function grad($pos: 100%) {
  @return linear-gradient(90deg, #f90 $pos, #444 0);
}

html {
  /* same as before */
    
  @supports (-ms-user-select: none) {
    background-image: grad(0%);
    animation-name: blinds-alt;
  }
}

@keyframes blinds-alt { to { background-image: grad() } }

Stopppositionen sind nicht das Einzige, was wir auf diese Weise animieren können. Dasselbe können wir für den Gradientenwinkel tun. Die Idee dahinter ist ziemlich dieselbe, außer dass unsere animation jetzt keine alternierende mehr ist und wir eine easeInOutBack-Timing-Funktion verwenden.

@function grad($ang: 1turn) {
  @return linear-gradient($ang, #f90 50%, #444 0);
}

html {
  background: grad(var(--ang, 0deg));
  animation: rot 2s cubic-bezier(.68, -.57, .26, 1.65) infinite;
  
  @supports (-ms-user-select: none) {
    background-image: grad(0turn);
    animation-name: rot-alt;
  }
}

@keyframes rot { to { --ang: 1turn; } }

@keyframes rot-alt { to { background-image: grad(); } }

Denken Sie daran, dass wir, genau wie bei den Stopppositionen, in Edge nur zwischen Gradientenwinkeln animieren können, die in der gleichen Einheit ausgedrückt sind. Daher funktioniert das Aufrufen unserer Sass-Funktion mit grad(0deg) anstelle von grad(0turn) nicht.

Und natürlich akzeptiert die CSS-Variable, die wir jetzt verwenden, Winkelwerte anstelle von Längen und Prozentangaben

CSS.registerProperty({
  name: '--ang', 
  syntax: '<angle>', 
  initialValue: '0deg', 
  inherits: true
});
Animated gif. Shows a top to bottom gradient with an abrupt change from grey to orange at 50%. The angle of this gradient is animated using a easeInOutBack timing function (which overshoots the end values at both ends).
Drehen (Live-Demo, nur Blink-Browser mit Flag und Edge).

Auf ähnliche Weise können wir auch radiale Verläufe animieren. Und das wirklich Coole am CSS-Variablenansatz ist, dass er es uns ermöglicht, verschiedene Komponenten des Verlaufs unterschiedlich zu animieren, was nicht möglich ist, wenn Verläufe als Ganzes animiert werden, wie es Edge tut (weshalb die folgenden Demos in Edge nicht so gut funktionieren).

Nehmen wir an, wir haben den folgenden radial-gradient()

$p: 9%;

html {
  --x: #{$p};
  --y: #{$p};
  background: radial-gradient(circle at var(--x) var(--y), #f90, #444 $p);
}

Wir registrieren die Variablen --x und --y

CSS.registerProperty({
  name: '--x', 
  syntax: '<length-percentage>', 
  initialValue: '0%', 
  inherits: true
});

CSS.registerProperty({
  name: '--y', 
  syntax: '<length-percentage>', 
  initialValue: '0%', 
  inherits: true
});

Dann fügen wir die Animationen hinzu

html {
  /* same as before */
  animation: a 0s ease-in-out -2.3s alternate infinite;
  animation-name: x, y;
  animation-duration: 4.1s, 2.9s;
}

@keyframes x { to { --x: #{100% - $p} } }
@keyframes y { to { --y: #{100% - $p} } }

Das Ergebnis sehen Sie unten

Animated GIF. Shows a moving glowing orange light on a grey background. This is achieved by animating the coordinates of the central point of a radial gradient independently with the help of CSS variables and Houdini.
Bewegliches Licht (Live-Demo, nur Blink-Browser mit Flag).

Wir können diese Technik des Animierens der verschiedenen benutzerdefinierten Eigenschaften, die wir in der Gradientenfunktion verwenden, nutzen, um die Jalousien in unserem ursprünglichen Beispiel anders schließen zu lassen, anstatt zurückzugehen. Dazu führen wir zwei weitere CSS-Variablen ein: --c0 und --c1

$c: #f90 #444;

html {
  --c0: #{nth($c, 1)};
  --c1: #{nth($c, 2)};
  background: linear-gradient(90deg, var(--c0) var(--pos, 0%), var(--c1) 0) 50%/ 5em;
}

Wir registrieren all diese benutzerdefinierten Eigenschaften

CSS.registerProperty({
  name: '--pos', 
  syntax: '<length-percentage>', 
  initialValue: '0%', 
  inherits: true
});

CSS.registerProperty({
  name: '--c0', 
  syntax: '<color>', 
  initialValue: 'red', 
  inherits: true
});

/* same for --c1 */

Wir verwenden die gleiche Animation wie zuvor für die Position des ersten Stopps --pos und führen zusätzlich zwei steps()-Animationen für die anderen beiden Variablen ein, die ihre Werte jedes Mal umschalten, wenn eine Iteration der ersten animation (die den Wert von --pos ändert) abgeschlossen ist

$t: 1s;

html {
  /* same as before */
  animation: a 0s infinite;
  animation-name: c0, pos, c1;
  animation-duration: 2*$t, $t;
  animation-timing-function: steps(1), ease-in-out;
}

@keyframes pos { to { --pos: 100%; } }

@keyframes c0 { 50% { --c0: #{nth($c, 2)} } }
@keyframes c1 { 50% { --c1: #{nth($c, 1)} } }

Und wir erhalten folgendes Ergebnis

Animated GIF. Shows the blinds effect with the blinds closing the other way. Once the vertical orange strips (openings) have expanded horizontally such that they cover the whole background, they don't start contracting again. Instead, vertical grey orange strips start expanding from nothing until they cover the whole background.
Eine weitere Version der Jalousien-Animation (Live-Demo, nur Blink-Browser mit Flag).

Wir können dies auch auf einen radial-gradient() anwenden (es ändert sich nichts außer der background-Deklaration)

background: radial-gradient(circle, var(--c0) var(--pos, 0%), var(--c1) 0);
Animated gif. We start with a grey background and we have an orange disc growing from nothing in the middle until it covers everything. Then we have a grey disc growing from nothing in the middle until it covers the entire background and we're back where we started from: a grey background.
Wachsende Scheiben (Live-Demo, nur Blink-Browser mit Flag).

Taktisch gesehen funktioniert das Gleiche auch für conic-gradient()

background: conic-gradient(var(--c0) var(--pos, 0%), var(--c1) 0);
Animated gif. We start with a grey background and we have an orange pie slice (circular sector) growing from nothing to covering everything around the central point. Then we have a grey pie slice growing from nothing to covering everything around the central point and we're back where we started from: a grey background.
Wachsende Keile (Live-Demo, nur Blink-Browser mit Flag).

Wiederholte Verläufe sind ebenfalls eine Option, die im radialen Fall einen wellenartigen Effekt erzeugt

$p: 2em;

html {
  /* same as before */
  background: repeating-radial-gradient(circle, 
    var(--c0) 0 var(--pos, 0px), var(--c1) 0 $p);
}

@keyframes pos { 90%, 100% { --pos: #{$p} } }
Animated gif. We start with a grey background and we have concentric orange circles growing outwards from really thin until they meet and cover everything, so now it looks like we have an orange background. Then we have grey circles growing outwards from really thin until they cover the entire background and we're back where we started from: a grey background.
Wellen (Live-Demo, nur Blink-Browser mit Flag).

Und ein Helix-/Strahlen-Effekt im konischen Fall

$p: 5%;

html {
  /* same as before */
  background: repeating-conic-gradient(
    var(--c0) 0 var(--pos, 0%), var(--c1) 0 $p);
}

@keyframes pos { 90%, 100% { --pos: #{$p} } }
Animated gif. We start with a grey background and we have orange rays growing clockwise from really thin until they meet and cover everything, so now it looks like we have an orange background. Then we have grey rays growing clockwise from really thin until they cover the entire background and we're back where we started from: a grey background.
Wachsende Strahlen (Live-Demo, nur Blink-Browser mit Flag).

Wir können eine weitere CSS-Variable hinzufügen, um die Dinge interessanter zu gestalten

$n: 20;

html {
  /* same as before */
  background: radial-gradient(circle at var(--o, 50% 50%), 
    var(--c0) var(--pos, 0%), var(--c1) 0);
  animation: a 0s infinite;
  animation-name: c0, o, pos, c1;
  animation-duration: 2*$t, $n*$t, $t;
  animation-timing-function: steps(1), steps(1), ease-in-out;
}

@keyframes o {
  @for $i from 0 to $n {
    #{$i*100%/$n} { --o: #{random(100)*1%} #{random(100)*1%} }
  }
}

Wir müssen diese Variable registrieren, damit das Ganze funktioniert

CSS.registerProperty({
  name: '--o', 
  syntax: '<length-percentage>', 
  initialValue: '50%', 
  inherits: true
});

Und das ist es! Das Ergebnis sehen Sie unten

Animated gif. We start with a grey background and we have an oranges disc, randomly positioned, growing from nothing until it covers everything, so now it looks like we have an orange background. Then we have grey disc, randomly positioned, growing from nothing until it covers the entire background and we're back where we started from: a grey background.
Zufällig positionierte wachsende Scheiben (Live-Demo, nur Blink-Browser mit Flag).

Ich würde sagen, die Zukunft des Änderns von Verläufen mit Keyframe-Animationen sieht ziemlich gut aus. Aber in der Zwischenzeit, für browserübergreifende Lösungen, bleibt der JavaScript-Weg der einzig gültige.