CSS/Custom properties (CSS-Variablen)

Aus SELFHTML-Wiki
< CSS
Wechseln zu: Navigation, Suche
In Stylesheets finden sich oft viele sich wiederholende Werte. So kann dieselbe Farbe an Hunderten von verschiedenen Stellen verwendet werden, was ein globales Suchen und Ersetzen erfordert, wenn sich diese Farbe ändern soll.

Mit custom properties (benutzerdefinierten Eigenschaften) können Sie einen Wert an einer Stelle festlegen, auf den dann an mehreren anderen Stellen verwiesen wird. Ein weiterer Vorteil sind semantische Bezeichner. So ist beispielsweise --main-text-color leichter zu verstehen als #00ff00, vor allem, wenn dieselbe Farbe auch in anderen Kontexten verwendet wird.

Vorläufer für den Einsatz von Variablen in CSS waren Präprozessoren wie SASS, die aber einige Nachteile haben:

  • Sie werden vor dem Laden des Dokuments zusammengestellt und können deshalb nicht auf Änderungen durch media queries reagieren
  • Sie können nicht von JavaScript ausgelesen oder verändert werden.

Custom Properties dagegen …

  • reagieren unmittelbar auf Änderungen im DOM und auf media queries
  • reagieren auf die Elementschachtelungen im DOM
  • ermöglichen es, mit setProperty() ein individuelles Theme anzubieten

Oft als CSS-Variablen bezeichnet, sind custom properties doch keine Variablen im eigentlichen Sinne.

Syntax

Die Kernsyntax von custom properties besteht aus zwei Teilen.

Erzeugen

Ein custom property wird genau wie die Deklaration einer regulären CSS-Eigenschaft erzeugt:

   name: wert;

Der Name eines Custom Property muss mit zwei Minuszeichen (Zeichencode \x2D) beginnen.

Der Wert eines Custom Property ist - mit gewissen Einschränkungen - eine beliebige Zeichenfolge und wird durch ein Semikolon abgeschlossen.

Definieren eines Custom Property
Im style-Attribut:
   <div style="--größe: 10px">
   </div>
In einem Stylesheet:
   body {
     --größe: 20em;
   }

Im Beispiel wurde einerseits im style-Attribut des div-Elements, sowie im Stylesheet im Regelsatz eine custom property namens --größe angelegt.
Die Buchstaben ö und ß haben einen Zeichencode über 127 und können deshalb problemlos im Namen verwendet werden.

Beachten Sie:
  • Im Gegensatz zu regulären Eigenschaften ist der Name eines Custom Property case-sensitive, d. h. die Namen --self und --Self bezeichnen unterschiedliche Eigenschaften.
  • Der Name eines Custom Property beginnt mit --. Die darauf folgenden Zeichen sind weitgehend frei wählbar, vor allem Buchstaben, Ziffern, das Minuszeichen und alle Unicode-Zeichen ab \x80, sogar Emojis.
    • Vermeiden Sie Zeichen, die in Stylesheets eine Sonderbedeutung haben. Sie müssten sie mit einem vorangestellten \ Zeichen maskieren.
    • Innerhalb des Variablennamens darf sich kein Leerzeichen befinden.
  • Variablen können nur Werte, aber keine Eigenschaften annehmen.
Die genauen Regeln dazu finden Sie im Artikel selbst definierte Namen.

Die Einschränkungen entstehen dadurch, dass an der Stelle, wo Sie das custom property verwenden, vor und nach Einsetzen des Wertes gültige CSS Syntax stehen muss. Sie können deshalb im Wert eines Custom Property nicht einfach eine runde oder geschweifte Klammer verwenden. Das ist anders, wenn der zugewiesene Wert in Anführungszeichen steht, aber in diesem Fall ist der Wert auch nur dort nutzbar, wo eine CSS Zeichenkette erwartet wird (z.B. als font-family oder content).


Verwenden

Um den Wert eines Custom Property zu nutzen, verwenden Sie die CSS-Funktion var().

  var(--name)
  var(--name, defaultwert)

Sie können var() überall dort verwenden, wo im CSS ein Wert notiert werden darf. Wie schon erwähnt, muss an der Stelle, wo var() notiert wird, gültige CSS-Syntax stehen. Die Klammer einer CSS-Funktion kann nicht aus dem custom property kommen, und die Anführungszeichen einer CSS Zeichenkette müssen im Wert des custom property enthalten sein. Wert und Einheit einer CSS-Wertangabe müssen ebenfalls vollständig in einer Variablen stehen.

Sie dürfen var() aber verwenden, um einen oder auch mehrere Bestandteile einer CSS Sammeleigenschaft beizusteuern.

Syntax
:root {
  --akzentfarbe: #c32e04;
  --akzentrahmen: solid
}
h1 {
  border: thin solid var(--akzentfarbe);
}
h1::before {
  color: var(--akzentfarbe);
  content: "Wichtig: ";
}

Im Beispiel wird im root für alle Elemente des Dokuments eine Farbe festgelegt. Diese erhält den Namen --akzentfarbe und ist durch die zwei vorangestellten Minuszeichen als custom property erkennbar.

Anschließend kann diese Variable im gesamtem Stylesheet mit der Funktion var() wieder aufgerufen werden.[1].

  • So bestimmt sie die Rahmenfarbe des h1-Elements.
  • Das Pseudoelement vor der h1-Überschrift erhält eine rote Textfarbe.


Mittlerweile unterstützen alle Browser custom properties und ein Fallback ist daher eigentlich nicht mehr nötig, kann aber als eindeutige Festlegung nach der CSS-Variable notiert werden:

Fallback mit festen Werten für custom properties
h1 {
  color: var(--akzentfarbe, red);
}

Die CSS-Funktion var() enthält neben dem Variablennamen noch einen durch Komma getrennten festen Wert.

Falls die Variable nicht vorhanden sein sollte, wird dieser verwendet. Falls der fallback-Wert nicht gültig ist, wird der Default-Wert unset verwendet.

Ältere Browser, die var() nicht verstehen, ignorieren auch diese Angabe und rendern dafür die Elemente in den Standardeinstellungen.

in JavaScript

Sie finden den Wert von Custom Propertys in der CSSStyleDeclaration, die Sie in der style-Eigenschaft eines DOM Elements vorfinden, sowie in dem StylePropertyMapReadOnly-Objekt, das sie von der computedStyleMap()-Methode des Elements bekommen. Wenn Sie mit registrierten Custom Propertys (siehe den Abschnitt Deklaration arbeiten, erhalten Sie den deklarierten Datentyp nur über die Stylemap.

Den aktuellen Wert können Sie für ein Element mittels window.getComputedStyle() ermitteln. Hierbei handelt es sich immer um die Zeichenkettendarstellung, und Längenangaben sind in px umgerechnet.

Wenn Sie einen Wert setzen möchten, dann benötigen Sie dazu die Methode setProperty des style-Objektes.

Deklarieren

Die Deklaration eines Custom Property ist optional. Ohne eine Deklaration ist sein Wertetyp unspezifiziert, mit anderen Worten: alles ist erlaubt, soweit es die Grundregeln von CSS erfüllt (paarige Klammern, paarige Anführungzeichen, etc), es wird an Kindelemente vererbt und sein Anfangswert ist undefiniert.

Zur Deklaration eines Custom Property kann man die JavaScript-Methode CSS.registerProperty verwenden, oder die @-Regel @property. Beide Wege bieten die gleichen Möglichkeiten.

Durch eine Registrierung werden drei Eigenschaften für das Custom Property festgelegt:

Syntax
CSS definiert von sich aus etliche Syntaxregeln für Werte. Diese finden sich in den jeweiligen Spezifikationen und können exakt so in der Registrierung angegeben werden. Also "<string>" für Zeichenketten, "<length>" für eine Längenangabe, "<color>" für eine Farbangabe, und so weiter. Eine vollständige Aufstellung der Syntaxwerte finden Sie auf der Referenzseite der @property-Regel.
Vererbungsverhalten
Sie können festlegen, ob das Custom Property auf Kindelemente vererbt wird oder nicht.
Anfangswert
Sie können festlegen, dass für den Fall, dass das Custom Property auf einem Element nicht gesetzt wurde, bei seinem Abruf ein bestimmter Wert geliefert wird.

Die Registrierung bietet folgende Vorteile:

  • Das Custom Property wird animierbar - ohne Registrierung wird hart zwischen den Keyframe-Werten (bzw. Anfangs- und Endwert) umgeschaltet.
  • Die Zuweisung eines ungültigen Wertes (z.B. Farbe statt Zeichenkette) wird beim Zuweisen ignoriert. Ohne Registrierung wird der ungültige Wert im Custom Property gespeichert und überschreibt damit einen eventuell geerbten Wert. Erst beim Verwenden des Custom Property merkt der Browser, dass der Wert ungültig ist und ignoriert das Property.
  • Vererbung kann gesteuert werden
  • Es kann ein Defaultwert vergeben werden, ohne dass bei jeder Verwendung der var()-Funktion ein Defaultwert hinzugefügt werden muss.
Farbpalette mit registrierten Custom Propertys
@property --textcolor { syntax: "<color>"; inherits: true; initial-value: white; }
@property --background { syntax: "<color>"; inherits: true; initial-value: #112; }

@media (prefers-color-scheme: light) {
  :root {
    --textcolor: #112;
    --background: white;
  }
}
body {
   color: var(--textcolor);
   background-color: var(--background);
}

Anwendungsbeispiele

Die folgenden Beispiele zeigen, dass man mit custom properties viel mehr als nur Farben festlegen kann:

Anwenden einer Farbpalette

Anwenden einer Farbpalette ansehen …
:root {
  --primary: #666;
  --hintergrund: #ccc;
  --akzent: #c32e04;
  --linkfarbe: #09c;
}

h2 {
  color: var(--primary);
  border-bottom: medium solid var(--akzent);
}

aside {
  color: var(--akzent);
  background-color: var(--primary);
  border: medium solid var(--akzent);
}

aside > h2 {
  color: var(--hintergrund);
  border-bottom: medium solid transparent;  
}

aside > label {
  color: black;
  background-color: var(--akzent);
}

Im Beispiel wird eine Farbpalette festgelegt. Durch die strukturelle Pseudoklasse root wird ein Gültigkeitsbereich für das gesamte HTML-Dokument festgelegt.

Diese Farbwerte werden dann im Stylesheet mehrfach verwendet.

ein maßgeschneidertes Theme

Mit den custom properties können nicht nur Farben, sondern auch Farbtöne und -variationen erzeugt werden[2]:

Farbton ansehen …
:root { --akzentfarbe: 195 46 4 }
h1 { color: rgb(var(--akzentfarbe)) }
h2 { color: rgb(var(--akzentfarbe) / 0.5) }

Für h2 wird die Akzentfarbe als rgb-Wert mit einem Transparenzwert von 0.5 (50% Deckkraft) berechnet.

Beachten Sie: Hier und im folgenden Beispiel werden nach neuer Syntax mehrere Werte durch Leerzeichen getrennt notiert.


Noch mehr Möglichkeiten ergeben sich bei der Verwendung von HSL-Werten[3]:

Erstellen Sie ihr eigenes Theme! ansehen …
:root {
  --baseHue: 240;   
  --primary: hsl(var(--baseHue) 80% 40%);
  --accent1: hsl(calc(var(--baseHue) - 231) 80% 40%);
  --backgr1: hsl(calc(var(--baseHue) - 231) 80% 40% / .15);
  --accent2: hsl(calc(var(--baseHue) - 200) 90% 50%);
  --backgr2: hsl(calc(var(--baseHue) - 200) 90% 50% / .15);  
}

Für das Dokument werden über den :root-Selektor eine Hauptfarbe und zwei Akzentfarben festgelegt. Dabei wird der Farbton über baseHue festgelegt und für die Akzentfarben mit der calc()-Funktion entsprechend verändert. Für die Akzentfarben gibt es noch hellere Töne für den Hintergrund. Insgesamt ist die Farbpalette an die SELFHTML-Farbtabelle angelehnt.

Beispiel
document.addEventListener('DOMContentLoaded', function () {
      document.querySelector('#huePicker').addEventListener('input', setHue);
});
	
function setHue() {
      const root = document.querySelector(':root');
      var huePicker = document.querySelector('#huePicker');
      root.style.setProperty('--baseHue', huePicker.value);
};

Beim Laden des Dokuments wird an den Schieberegler ein EventListener angehängt. Sobald er geändert wird, feuert das input-Event und der Wert wird mit setProperty der custom property baseHue zugewiesen.

Das Beispiel von Keith Clark verwendet Farbwähler (input type="color"), mit denen eine Hauptfarbe und eine Kontrastfarbe ausgewählt werden.

Countdown

Bis jetzt benötigten Sie JavaScript, um einen Countdown herunterzuzählen. Mit custom properties können Sie dies nur mit CSS erreichen.[4]

Countdown-Timer ansehen …
<div class="time-bar" style="--duration: 9;">
  <div></div>
</div>

Als Zeitleiste wird ein div verwendet (ein meter-Element wäre evtl. semantisch passender, bringt jedoch bereits viele eigene Formatierungen mit).

Countdown-Timer ansehen …
.time-bar div {
  height: 1em;
  background: linear-gradient(to bottom, red, #c32e04);
  animation: roundtime calc(var(--duration) * 1s) steps(var(--duration)) forwards;
  transform-origin: left center;
}

@keyframes roundtime {
  to {
    /* More performant than animating `width` */
    transform: scaleX(0);
  }
}

Bereits im HTML wurde im style-Attribut des Container-divs eine CSS-Variable --duration festgelegt. Sie wird nun 2x verwendet:

  • steps zerlegt die Animation in einzelne Schritte, ohne extra Wegpunkte festlegen zu müssen.
  • animation-duration erfordert einen Wert mit Zeiteinheit: calc(var(--duration) * 1s) fügt an die Zahl die Einheit s an.
Beachten Sie: Es ist performanter, die Breite des div durch transform:scaleX() anstelle von width zu animieren.

SVG und CSS

Längeneinheiten und custom properties

In HTML benötigen Elemente normalerweise keine Positionierung, da sie nacheinander im Elementfluss liegen. SVG-Objekte werden jedes für sich gezeichnet und positioniert. Bei mehreren Objekten mit jeweils gleichem Abstand zueinander müssen die Koordinaten immer wieder neu berechnet werden. Dies geht mit CSS einfacher:

Abstände berechnen ansehen …
.ball {
    --distance: calc(var(--d, 1) * (2.5 * var(--radius)));
    --radius: 20px;
    cx: var(--distance);
    cy: var(--radius);
    r: var(--radius);
}

.ball:nth-child(1) {
  fill: var(--color-1);
}

.ball:nth-child(2) {
  fill: var(--color-2);
    --d: 2;
}

.ball:nth-child(3) {
  fill: var(--color-3);
    --d: 3;
}
...

Das Beispiel enthält drei custom properties:

  • --radius erhält einen festen Pixelwert von 20px. (Eigentlich besteht das Koordinatensystem von SVG aus dimensionslosen Einheiten; Firefox benötigt bei Geometrieattributen in CSS eine Angabe in Pixeln.)
  • --var-distance besteht aus einer längeren Berechnung mit calc():
    • (2.5 * var(--radius) verwendet den eben erwähnten Radius und multipliziert ihn mit einem Faktor 2.5. Dieser Wert wird z.B. für den Abstand des ersten Balls zum linken Rand benötigt.
    • calc(var(--d, 1) * ...) multipliziert das obere Zwischenergebnis mit einer Variable --d
  • Die Variable --d wird per CSS über den nth-child immer wieder neu gesetzt.

Die Berechnung von --distance löst ein vermeintliches Problem von custom properties, dass das Hinzufügen von Einheiten wie in JavaScript nicht möglich wäre:[5]

.icon {
  --scale: 1;

  /* this doesn't work */
  font-size: var(--scale) + 'px';
}

Mit Hilfe der calc()-Funktion gelingt dies problemlos:

.icon {
  --scale: 1;

  /* this works */
  font-size: calc(var(--scale) * 1rem);
}

SVG mit CSS-Animationen

Auch in CSS-Animationen gibt es z.B. bei Verzögerungen Werte, die aufeinander aufbauen.

gestaffelte Animation ansehen …
.ball {
    --delay: calc(var(--i, 1) * 100ms);
    --distance: calc(var(--d, 1) * (2.5 * var(--radius)));
    --radius: 20px;
    cx: var(--distance);
    cy: var(--radius);
    r: var(--radius);
    animation: moveCircle 1200ms var(--delay) ease-in-out  infinite;
}

.ball:nth-child(1) {
  fill: var(--color-1);
}

.ball:nth-child(2) {
  fill: var(--color-2);
    --d: 2;
    --i: 2;
}

.ball:nth-child(3) {
  fill: var(--color-3);
    --d: 3;
    --i: 3;
}

Wie im oberen Beispiel setzt sich die Variable --delay aus einer Berechnung zusammen: Die Zählvariable --i wird über den nth-child-Selektor immer wieder neu gesetzt und mit einem gegebenen Zeitwert (von hier 100ms) multipliziert.

Leider muss diese Variable --i wie auch --d immer wieder explizit gesetzt werden. Um die Anzahl der Objekte automatisch zu erfassen, müsste man JavaScript einsetzen. Deshalb gibt es einen Vorschlag eine sibling-count()-Funktion einzuführen, die dies bereits im CSS erledigen könnte.[6]

clip-path und vendor-prefixes

Eine der Begleiterscheinungen von CSS3 waren die Browserpräfixe, die den damals neuen Eigenschaften vorangestellt wurden. Heute gibt es nur noch wenige Eigenschaften, die z. B. ein zusätzlichen Regelsatz mit -webkit- benötigen.

Bei Beschneidungen erfordert ein Einsatz der clip-path-Eigenschaft solch einen doppelten Regelsatz. Damit bei einer späteren Anpassung der Wert nur einmal geändert werden muss, können auch hier Custom properties helfen:

keine doppelte Arbeit mehr:
.img {
  --clip: polygon(0 0, 100% 0, 50% 100%, 0 100%);

  -webkit-clip-path: var(--clip);
  clip-path: var(--clip);
}

Der Wert wird als CSS-Variable --clip festgelegt und beiden Regelsätze zugewiesen.

Noch besser wäre es, wenn man den Beschneidungspfad selbst durch custom properties vereinfachen könnte. Michelle Barker zeigte das in diesem CodePen.[7]

Weiterführendes

Benutzerdefinierte Eigenschaften verhalten sich genau wie normale, vererbbare CSS-Eigenschaften: Sie sind auch auf Kind-Elementen verfügbar und sie beachten die Regeln für Kaskade und Spezifität, d.h. benutzerdefinierte Eigenschaften, die an einer Stelle festgelegt werden, können durch eine spezifischere Regel überschrieben werden.

Zusammenspiel der Regeln

Eine wichtige Eigenschaft von custom properties ist, dass var() aufgelöst wird, wenn die Eigenschaft, die die var()-Angabe enthält, auf ein HTML- oder SVG-Element angewendet wird. Wird die so gesetzte Eigenschaft weiter vererbt, ist der Wert festgelegt und nicht mehr variabel.

Custom Properties und Vererbung ansehen …
<style>
body { --farbe: red; }
ul { color: var(--farbe); }
li { --farbe: blue; }
</style>
...
<body>
  Hallo
  <ul>
    <li>Welt</li>
    <li>Selfhtml</li>
  </ul>
</body>
  1. Der body erhält ein custom property --farbe mit dem Wert red.
  2. Für das ul-Element wird für die Eigenschaft color der berechnete Wert des custom property --farbe festgelegt. Dieser wird, falls es keine spezifischeren Festlegungen gibt, an die Kindelemente weitervererbt.
  3. Für li wird der Wert für --farbe neu gesetzt.
Aufgabe: Überlegen Sie, welche Textfarbe die li-Elemente haben.

Lösung anzeigenverbergenFür li wird der Wert für --farbe neu gesetzt.
Allerdings wurde der neue Wert nicht der color-Eigenschaft zugewiesen; deshalb wird der vorher zugewiesene Wert für ul vererbt.
Die li-Elemente werden rot dargestellt.


Vererbung unterschiedlicher Werte ansehen …
<style>
p { color: var(--farbe); }
section.a { --farbe:red; }
section.b { --farbe:blue; }
</style>
...
<body>
  <section class="a">
    <p>Hallo Welt</p>
  </section>
  <section class="b">
    <p>Hallo Welt</p>
  </section>
  <p>Absatz außerhalb einer section</p>

Hier ist es so, dass die erste CSS Regel auf beide p Elemente innerhalb der section-Elemente angewendet wird, aber jeweils im Moment der Anwendung unterschiedliche Werte für --farbe vom section-Element vererbt werden. Deswegen wird der erste Abschnitt rot dargestellt und der zweite Abschnitt blau.

Aufgabe: Überlegen Sie, welche Textfarbe der untere Absatz hat.

Sind das jetzt Variablen?

Auch wenn es immer wieder heißt, dass Custom properties keine Variablen wären, werden die beiden Begriffe oft nebeneinander benutzt:[8]

Custom properties (sometimes referred to as CSS variables or cascading variables) … You can define multiple fallback values when the given variable is not yet defined.

MDN: Using CSS custom properties (variables)[9]

Auch die Spezifikation verwendet den Begriff Variable, unterscheidet aber: Eine benutzerdefinierte Eigenschaft ist keine Variable, ermöglicht aber die Festlegung einer Variable.

Jede Eigenschaft kann mit der var()-Funktion Variablen verwenden, deren Werte durch die zugehörigen benutzerdefinierten Eigenschaften definiert sind.


Css-variables-example.png


Die benutzerdefinierte Eigenschaft --accent-background verwendet die Variable var(--main-color), deren Wert durch die benutzerdefinierte Eigenschaft --main-color definiert ist.

Zukunftsmusik: custom media queries

Wenn Sie benutzerdefinierte CSS-Eigenschaften (CSS-Variablen) verwenden und sie in einer Medienabfrage verwenden wollten, haben Sie wahrscheinlich festgestellt, dass Sie benutzerdefinierte Eigenschaften in diesem Zusammenhang nicht verwenden können.[11]

@media (max-width: var(--mobile-breakpoint)) {...}

Benutzerdefinierte Media-Queries sind seit Jahren in der Spezifikation enthalten. Allerdings scheint es kein großes Interesse an der Implementierung zu geben. Weder die MDN noch caniuse.com kennen das Feature bisher.

custom media queries(noch nicht implementiert)
@custom-media --narrow-window (max-width: 30em);

@media (--narrow-window) {
  /* narrow window styles */
}

Siehe auch

  • CSS-Formatierung des Shadow DOM
    Das SVG-Use-Element kann aus beliebig vielen Teil-Elementen bestehen, die im Shadow DOM vorhanden sind, aber nicht durch CSS formatiert werden können. MIt Custom properties können auch einzelne Bestandteile des use-Elements formatiert werden.

SVG/Tutorials/Gruppierungen#Styling des use-Elements

Quellen

  1. CSSWG: Using Cascading Variables: the var() notation
  2. SELF-Forum: Farben in Abhängigkeit von Grundfarbe von Gunnar Bittersmann vom 05.03.2020
  3. SELF-Forum: Farben in Abhängigkeit von Grundfarbe von Rolf B. vom 05.03.2020
  4. css-tricks.com: Timer Bars in CSS with Custom Properties von Chris Coyier, Aug 18, 2020
  5. How to append a unit to a unitless css custom property with calc() Kevin Powell, 17.03.2019
  6. github.com: [css-values] Proposal: add sibling-count() and sibling-index() Vorschlag vom 04.12.2019
  7. CSS {In Real Life}: 7 Uses for CSS Custom Properties vom 09.12.2019
  8. webplatformnews: CSS custom properties are not variables
  9. MDN:Using CSS custom properties (variables)
  10. Wikipedia: Variable (Programmierung)
  11. Stefan Judis: Can we have custom media queries, please? vom 08.08.2021

Weblinks

Zikaden-Prinzip für unregelmäßig wirkende Strukturen