Die Macht (und der Spaß) von Scope mit CSS Custom Properties

Avatar of Jhey Tompkins
Jhey Tompkins am

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

Sie sind wahrscheinlich bereits ein wenig mit CSS-Variablen vertraut. Wenn nicht, hier ist eine Zweisekundenübersicht: Sie heißen eigentlich Custom Properties, Sie setzen sie in Deklarationsblöcken wie --size: 1em und verwenden sie als Werte wie font-size: var(--size);, sie unterscheiden sich von Preprocessor-Variablen (z. B. sie kaskadieren) und hier ist ein Leitfaden mit weitaus mehr Informationen.

Aber nutzen wir sie ihr volles Potenzial? Verfallen wir in alte Gewohnheiten und übersehen wir Gelegenheiten, bei denen Variablen die Menge an Code, die wir schreiben, erheblich reduzieren könnten?

Dieser Artikel wurde durch einen kürzlichen Tweet von mir über die Verwendung von CSS-Variablen zur Erstellung dynamischer Animationsverhaltensweisen angeregt.

CSS-Variablen sind fantastisch, oder? Aber die Macht des Scopes wird oft übersehen. Nehmen Sie zum Beispiel diese Demo, 3 verschiedene Animationen, aber nur 1 definierte Animation 💪 Das bedeutet dynamische Animationen 😎 https://#/VN02NlC4G8 via @CodePen #CSS #animation #webdev #webdesign #coding pic.twitter.com/ig8baxr7F3

— Jhey @ NodeConfEU 2019 📍🇮🇪⌨️ (@jh3yy) 5. November 2019

Schauen wir uns ein paar Beispiele an, bei denen CSS-Variablen verwendet werden können, um ziemlich coole Dinge zu tun, die wir vielleicht noch nicht in Betracht gezogen haben.

Grundlegende Scope-Vorteile

Das einfachste und wahrscheinlich häufigste Beispiel sind gescopten Farben. Und was ist unsere Lieblingskomponente, die wir mit Farbe verwenden? Der Button. 😅

Betrachten wir die Standardkonfiguration von primären und sekundären Buttons. Beginnen wir mit einem einfachen Markup, das eine BEM-Syntax verwendet.

<button class="button button--primary">Primary</button>
<button class="button button--secondary">Secondary</button>

Traditionell würden wir vielleicht so etwas tun, um sie zu stylen

.button {
  padding: 1rem 1.25rem;
  color: #fff;
  font-weight: bold;
  font-size: 1.25rem;
  margin: 4px;
  transition: background 0.1s ease;
}

.button--primary {
  background: hsl(233, 100%, 50%);
  outline-color: hsl(233, 100%, 80%);
}

.button--primary:hover {
  background: hsl(233, 100%, 40%);
}

.button--primary:active {
  background: hsl(233, 100%, 30%);
}

.button--secondary {
  background: hsl(200, 100%, 50%);
  outline-color: hsl(200, 100%, 80%);
}

.button--secondary:hover {
  background: hsl(200, 100%, 40%);
}

.button--secondary:active {
  background: hsl(200, 100%, 30%);
}

Das ist eine Menge Code für etwas, das nicht besonders komplex ist. Wir haben nicht viele Stile hinzugefügt und viele Regeln hinzugefügt, um den verschiedenen Zuständen und Farben des Buttons gerecht zu werden. Mit einer gescopten Variable könnten wir den Code erheblich reduzieren.

In unserem Beispiel ist der einzige Wert, der sich zwischen den beiden Button-Varianten unterscheidet, die Farbe (Hue). Lassen Sie uns diesen Code etwas umgestalten. Wir werden das Markup nicht ändern, aber wenn wir die Stile etwas aufräumen, erhalten wir Folgendes

.button {
  padding: 1rem 1.25rem;
  color: #fff;
  font-weight: bold;
  font-size: 1.25rem;
  margin: 1rem;
  transition: background 0.1s ease;
  background: hsl(var(--hue), 100%, 50%);
  outline-color: hsl(var(--hue), 100%, 80%);

}
.button:hover {
  background: hsl(var(--hue), 100%, 40%);
}

.button:active {
  background: hsl(var(--hue), 100%, 30%);
}

.button--primary {
  --hue: 233;
}

.button--secondary {
  --hue: 200;
}

Das reduziert nicht nur den Code, sondern erleichtert auch die Wartung erheblich. Ändern Sie die Kern-Button-Stile an einer Stelle und alle Varianten werden aktualisiert! 🙌

Ich würde es wahrscheinlich dort belassen, um es Entwicklern, die diese Buttons verwenden möchten, zu erleichtern. Aber wir könnten es noch weiter treiben. Wir könnten die Variable direkt auf das Element einfügen und die Klassendefinitionen vollständig entfernen. 😲

<button class="button" style="--hue: 233;">Primary</button>
<button class="button" style="--hue: 200;">Secondary</button>

Jetzt brauchen wir diese nicht mehr. 👍

.button--primary {
  --hue: 233;
}

.button--secondary {
  --hue: 200;
}

Das Einbetten dieser Variablen ist möglicherweise nicht das Beste für Ihr nächstes Designsystem oder Ihre nächste App, aber es eröffnet Möglichkeiten. Zum Beispiel, wenn wir eine Button-Instanz hätten, bei der wir die Farbe überschreiben müssten.

button.button.button--primary(style=`--hue: 20;`) Overridden

Spaß mit Inline-Variablen

Eine weitere Möglichkeit ist, damit ein wenig Spaß zu haben. Dies ist eine Technik, die ich für viele der Pens verwende, die ich auf CodePen erstelle. 😉

Sie schreiben vielleicht geradliniges HTML, aber in vielen Fällen verwenden Sie vielleicht ein Framework wie React oder einen Präprozessor wie Pug, um Ihr Markup zu schreiben. Diese Lösungen ermöglichen es Ihnen, JavaScript zu nutzen, um zufällige Inline-Variablen zu erstellen. Für die folgenden Beispiele werde ich Pug verwenden. Pug ist eine auf Einrückungen basierende HTML-Templating-Engine. Wenn Sie mit Pug nicht vertraut sind, keine Angst! Ich werde versuchen, das Markup einfach zu halten.

Beginnen wir damit, die Farbe für unsere Buttons zu randomisieren

button.button(style=`--hue: ${Math.random() * 360}`) First

Mit Pug können wir ES6-Template-Literale verwenden, um randomisierte CSS-Variablen einzufügen. 💪

Animationsänderungen

Jetzt, da wir die Möglichkeit haben, zufällige Eigenschaften für ein Element zu definieren, was könnten wir noch tun? Nun, eine übersehene Gelegenheit ist die Animation. Richtig, wir können die Variable selbst nicht animieren, wie hier

@keyframes grow {
  from { --scale: 1; }
  to   { --scale: 2; }
}

Aber wir können dynamische Animationen basierend auf gescopten Variablen erstellen. Wir können das Verhalten der Animation im laufenden Betrieb ändern! 🤩

Beispiel 1: Der aufgeregte Button

Erstellen wir einen Button, der vor sich hin schwebt und sich dann aufregt, wenn wir ihn mit der Maus darüber fahren.

Beginnen wir mit dem Markup

button.button(style=`--hue: ${Math.random() * 360}`) Show me attention

Eine einfache Schwebeanimation könnte so aussehen

@keyframes flow {
  0%, 100% {
    transform: translate(0, 0);
  }
  50% {
    transform: translate(0, -25%);
  }
}

Das gibt uns so etwas

Ich habe einen kleinen Schatten als Extra hinzugefügt, aber er ist nicht entscheidend. 👍

Machen wir es so, dass unser Button aufgeregt wird, wenn wir mit der Maus darüber fahren. Nun, wir könnten einfach die verwendete Animation zu etwas wie diesem ändern

.button:hover {
  animation: shake .1s infinite ease-in-out;
}

@keyframes shake {
  0%, 100% {
    transform: translate(0, 0) rotate(0deg);
  }
  25% {
    transform: translate(-1%, 3%) rotate(-2deg);
  }
  50% {
    transform: translate(1%, 2%) rotate(2deg);
  }
  75% {
    transform: translate(1%, -2%) rotate(-1deg);
  }
}

Und es funktioniert

Aber wir müssen eine weitere Keyframes-Definition einführen. Was wäre, wenn wir die beiden Animationen zu einer zusammenführen könnten? Sie sind sich in Bezug auf die Struktur nicht allzu unähnlich.

Wir könnten versuchen

@keyframes flow-and-shake {
  0%, 100% {
    transform: translate(0, 0) rotate(0deg);
  }
  25%, 75% {
    transform: translate(0, -12.5%) rotate(0deg);
  }
  50% {
    transform: translate(0, -25%) rotate(0deg);
  }
}

Obwohl dies funktioniert, landen wir bei einer Animation, die aufgrund der Translationsschritte nicht ganz so flüssig ist. Was könnten wir also noch tun? Finden wir einen Kompromiss, indem wir die Schritte bei 25 % und 75 % entfernen.

@keyframes flow-and-shake {
  0%, 100% {
    transform: translate(0, 0) rotate(0deg);
  }
  50% {
    transform: translate(0, -25%) rotate(0deg);
  }
}

Es funktioniert wie erwartet, aber hier kommt der Trick: Aktualisieren wir unseren Button mit einigen Variablen.

.button {
  --y: -25;
  --x: 0;
  --rotation: 0;
  --speed: 2;
}

Lassen Sie uns sie nun in die Animationsdefinition einfügen, zusammen mit den Animationseigenschaften des Buttons.

.button {
  animation-name: flow-and-shake;
  animation-duration: calc(var(--speed) * 1s);
  animation-iteration-count: infinite;
  animation-timing-function: ease-in-out;
}

@keyframes flow-and-shake {
  0%, 100% {
    transform: translate(calc(var(--x) * -1%), calc(var(--y) * -1%))
      rotate(calc(var(--rotation) * -1deg));
  }
  50% {
    transform: translate(calc(var(--x) * 1%), calc(var(--y) * 1%))
      rotate(calc(var(--rotation) * 1deg));
  }
}

Alles ist gut. 👍

Ändern wir diese Werte, wenn der Button gehovert wird

.button:hover {
  --speed: .1;
  --x: 1;
  --y: -1;
  --rotation: -1;
}

Schön! Jetzt hat unser Button zwei verschiedene Arten von Animationen, die aber über einen einzigen Satz von Keyframes definiert werden. 🤯

Lassen Sie uns damit noch ein bisschen mehr Spaß haben. Wenn wir es noch weiter treiben, können wir den Button ein bisschen spielerischer gestalten und vielleicht aufhören zu animieren, wenn er aktiv ist. 😅

Beispiel 2: Blasen

Nachdem wir nun verschiedene Techniken für das, was wir mit der Macht des Scopes tun können, durchgegangen sind, fassen wir alles zusammen. Wir werden eine zufällig generierte Blasenszene erstellen, die stark auf gescopten CSS-Variablen basiert.

Beginnen wir mit der Erstellung einer Blase. Einer statischen Blase.

.bubble {
  background: radial-gradient(100% 115% at 25% 25%, #fff, transparent 33%),
    radial-gradient(15% 15% at 75% 75%, #80dfff, transparent),
    radial-gradient(100% 100% at 50% 25%, transparent, #66d9ff 98%);
  border: 1px solid #b3ecff;
  border-radius: 100%;
  height: 50px;
  width: 50px;
}

Wir verwenden background mit mehreren Werten und einen border, um den Blasen-Effekt zu erzielen – aber er ist nicht sehr dynamisch. Wir wissen, dass der border-radius immer derselbe sein wird. Und wir wissen, dass die Struktur des border und background sich nicht ändern wird. Aber die Werte innerhalb dieser Eigenschaften und die anderen Eigenschaftswerte könnten alle zufällig sein.

Lassen Sie uns den CSS refaktorieren, um Variablen zu nutzen

.bubble {
  --size: 50;
  --hue: 195;
  --bubble-outline: hsl(var(--hue), 100%, 50%);
  --bubble-spot: hsl(var(--hue), 100%, 75%);
  --bubble-shade: hsl(var(--hue), 100%, 70%);
  background: radial-gradient(100% 115% at 25% 25%, #fff, transparent 33%),
    radial-gradient(15% 15% at 75% 75%, var(--bubble-spot), transparent),
    radial-gradient(100% 100% at 50% 25%, transparent, var(--bubble-shade) 98%);
  border: 1px solid var(--bubble-outline);
  border-radius: 100%;
  height: calc(var(--size) * 1px);
  width: calc(var(--size) * 1px);
}

Das ist ein guter Anfang. 👍

Fügen wir weitere Blasen hinzu und nutzen wir den Inline-Scope, um sie zu positionieren und zu dimensionieren. Da wir mehr als einen Wert randomisieren werden, ist es praktisch, eine Funktion zum Generieren einer Zufallszahl im Bereich für unser Markup zu haben.

- const randomInRange = (max, min) => Math.floor(Math.random() * (max - min + 1)) + min

Mit Pug können wir Iteration nutzen, um eine große Menge an Blasen zu erstellen

- const baseHue = randomInRange(0, 360)
- const bubbleCount = 50
- let b = 0
while b < bubbleCount
  - const size = randomInRange(10, 50)
  - const x = randomInRange(0, 100)
  .bubble(style=`--x: ${x}; --size: ${size}; --hue: ${baseHue}`)
  - b++

Die Aktualisierung unserer .bubble-Styles ermöglicht es uns, die neuen Inline-Variablen zu nutzen.

.bubble {
  left: calc(var(--x) * 1%);
  position: absolute;
  transform: translate(-50%, 0);
}

Was uns eine zufällige Menge von Blasen gibt

Lassen Sie uns es noch weiter treiben und diese Blasen animieren, damit sie von oben nach unten schweben und ausblenden.

.bubble {
  animation: float 5s infinite ease-in-out;
  top: 100%;
}

@keyframes float {
  from {
    opacity: 1;
    transform: translate(0, 0) scale(0);
  }
  to {
    opacity: 0;
    transform: translate(0, -100vh) scale(1);
  }
}

Das ist ziemlich langweilig. Sie tun alle dasselbe zur gleichen Zeit. Also randomisieren wir die Geschwindigkeit, Verzögerung, Endskalierung und Distanz, die jede Blase zurücklegen wird.

- const randomInRange = (max, min) => Math.floor(Math.random() * (max - min + 1)) + min
- const baseHue = randomInRange(0, 360)
- const bubbleCount = 50
- let b = 0
while b < bubbleCount
  - const size = randomInRange(10, 50)
  - const delay = randomInRange(1, 10)
  - const speed = randomInRange(2, 20)
  - const distance = randomInRange(25, 150)
  - const scale = randomInRange(100, 150) / 100
  - const x = randomInRange(0, 100)
  .bubble(style=`--x: ${x}; --size: ${size}; --hue: ${baseHue}; --distance: ${distance}; --speed: ${speed}; --delay: ${delay}; --scale: ${scale}`)
  - b++

Und jetzt aktualisieren wir unsere Stile

.bubble {
  animation-name: float;
  animation-duration: calc(var(--speed) * 1s);
  animation-delay: calc(var(--delay) * -1s);
  animation-iteration-count: infinite;
  animation-timing-function: ease-in-out;
}

@keyframes float {
  from {
    opacity: 1;
    transform: translate(-50%, 0) scale(0);
  }
  to {
    opacity: 0;
    transform: translate(-50%, calc(var(--distance) * -1vh)) scale(var(--scale));
  }
}

Und wir erhalten dies

Mit etwa 50 Zeilen Code können Sie eine zufällig generierte animierte Szene erstellen, indem Sie die Macht des Scopes verfeinern! 💪

Das ist alles!

Wir können mit sehr wenig Code ziemlich coole Dinge erstellen, indem wir CSS-Variablen nutzen und einige kleine Tricks anwenden.

Ich hoffe, dieser Artikel hat ein Bewusstsein für die Macht des CSS-Variablen-Scopes geschaffen, und ich hoffe, Sie werden die Macht verfeinern und weitergeben 😎

Alle Demos in diesem Artikel sind in dieser CodePen-Sammlung verfügbar.