Benutzerdefinierte Eigenschaften (CSS-Variablen) dynamischer gestalten

Avatar of Dan Wilson
Dan Wilson on

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

CSS Custom Properties (vielleicht besser als CSS-Variablen verstanden) bieten uns Möglichkeiten, Code kürzer zu gestalten, sowie neue Wege der Arbeit mit CSS, die vorher nicht möglich waren. Sie können das, was Präprozessor-Variablen können... aber auch noch viel mehr. Egal, ob Sie ein Fan der deklarativen Natur von CSS sind oder den Großteil Ihrer Stil-Logik lieber in JavaScript handhaben, Custom Properties bieten für jeden etwas.

Die meiste Kraft kommt von zwei einzigartigen Fähigkeiten von Custom Properties

  • Der Kaskadeneffekt (Cascade)
  • Die Möglichkeit, Werte mit JavaScript zu ändern

Noch mehr Kraft wird freigesetzt, wenn Sie Custom Properties mit anderen bereits vorhandenen CSS-Konzepten wie calc() kombinieren.

Die Grundlagen

Sie können Custom Properties verwenden, um effektiv das zu tun, was Variablen in Präprozessoren wie Sass bieten – einen globalen oder bereichsspezifischen Variablenwert festlegen und ihn dann später in Ihrem Code verwenden. Aber dank des Kaskadeneffekts können Sie neue Eigenschaftswerte innerhalb einer spezifischeren Regel vergeben.

Diese Kaskade kann zu mehreren interessanten Ansätzen führen, wie von Violet Peña mit einem Überblick über die wichtigsten Vorteile von Variablen und Chris mit einer Zusammenfassung von Website-Theming-Optionen gezeigt wurde.

Diese Vorteile der Kaskade werden seit einigen Jahren diskutiert, gehen aber oft in der Unterhaltung unter, obwohl sie eine Schlüsselfunktion sind, die sie von Präprozessoren unterscheidet. Amelia Bellamy-Royds diskutierte sie 2014 im Kontext von SVG und use, und Philip Walton bemerkte 2015 viele dieser allgemeinen Vorteile der Kaskade, und letztes Jahr zeigte Gregor Adams, wie sie in einem minimalen Grid-Framework verwendet werden können. Die Nutzung der Kaskade ist wahrscheinlich der einfachste Weg, um mit Custom Properties mit Blick auf progressive Verbesserung zu beginnen.

Okay. Jetzt, da wir wissen, dass Custom Properties nativ einige Funktionen bieten, die Präprozessoren haben, und einige neue Nutzungen dank der Kaskade – bieten sie uns auch etwas, das wir einfach noch nie tun konnten?

Aber sicher!

Eigenschaften individualisieren

Alle Eigenschaften, die mehrere Teile haben, können nun anders verwendet werden. Mehrere backgrounds können getrennt werden, und mehrere transition-durations können einzeln aufgeschlüsselt werden. Anstatt eine Regel wie transform: translateX(10vmin) rotate(90deg) scale(.8) translateY(5vmin) zu verwenden, können Sie eine Regel mit mehreren benutzerdefinierten Eigenschaften festlegen und die Werte danach unabhängig voneinander ändern.

.view { 
  transform: 
    translateX(var(--tx, 0))
    rotate(var(--deg, 0))
    scale(var(--scale, 1))
    translateY(var(--ty, 0));
}
.view.activated {
  --tx: 10vmin;
  --deg: 90deg;
}
.view.minimize {
  --scale: .8;
}
.view.priority {
  --ty: 10vmin;
}

Es braucht ein wenig zur Initialisierung, aber dann dieser kleine zusätzliche Aufwand am Anfang bereitet Sie darauf vor, jede Transformationsfunktion unabhängig zu ändern, basierend auf den Bedürfnissen der Klasse/Selektor-Regel. Ihr Markup kann dann jede oder alle der auf jedem .view-Element definierten Klassen enthalten und das transform wird entsprechend aktualisiert.

Während unabhängige Transformationseigenschaften kommen (und zu dieser Zeit werden translate, scale und rotate erste Bürger sein), sind sie derzeit nur in Chrome hinter einem Flag. Mit Custom Properties können Sie diese Funktionalität heute mit mehr Unterstützung erhalten (und der zusätzlichen Möglichkeit, Ihre eigene Reihenfolge der Funktionen zu definieren, da rotate(90deg) translateX(10vmin) anders ist als translateX(10vmin) rotate(90deg), zum Beispiel).

Wenn es Ihnen nichts ausmacht, dass sie die gleichen Timing-Optionen haben, können sie sogar flüssig animiert werden, wenn Sie transition verwenden, wenn Sie eine der Variablen ändern. Das ist irgendwie magisch.

Siehe den Pen CSS Variables + Transform = Individual Properties (with Inputs) von Dan Wilson (@danwilson) auf CodePen.

Von ohne Einheiten zu allen Einheiten

Sie können auf diesen Konzepten aufbauen, wenn Sie sie mit calc() kombinieren. Anstatt Variablen wie oben immer mit Einheiten festzulegen (--card-width: 10vmin oder --rotation-amount: 1turn), können Sie die Einheiten weglassen und sie in mehr Stellen verwenden, die miteinander in Beziehung stehen. Nun können die Werte in unseren Custom Properties dynamischer sein, als sie es bereits waren.

Obwohl calc() schon seit einigen Jahren existiert, war es wohl am nützlichsten, wenn man ein Ergebnis aus der Addition von Werten mit unterschiedlichen Einheiten erzielen wollte. Zum Beispiel haben Sie eine flüssige width in Prozent-Einheiten, die um 50px verkürzt werden muss (width: calc(100% - 50px)). calc() ist jedoch zu mehr fähig.

Andere Operationen wie Multiplikation sind innerhalb von calc erlaubt, um einen Wert anzupassen. Das Folgende ist gültig und gibt uns einen Hinweis darauf, dass die Transformationen und Filter miteinander in Beziehung stehen, da sie alle die Zahl 10 verwenden.

.colorful {
  transform: 
    translateX(calc(10 * 1vw))
    translateY(calc(10 * 1vh));
  filter: hue-rotate(calc(10 * 4.5deg));
}

Dies ist wahrscheinlich kein alltäglicher Anwendungsfall, da es sich um eine Berechnung handelt, die der Browser nicht berechnen muss. 10 * 1vw wird immer 10vw sein, sodass die Calc nichts bringt. Es kann nützlich sein, wenn Sie einen Präprozessor mit Schleifen verwenden, aber das ist ein kleinerer Anwendungsfall und kann normalerweise ohne CSS calc() erledigt werden.

Aber was wäre, wenn wir die wiederholte 10 durch eine Variable ersetzen? Sie können Werte aus einem einzelnen Wert an mehreren Stellen basieren, sogar mit unterschiedlichen Einheiten, und sie für zukünftige Änderungen öffnen. Das Folgende ist dank einheitenloser Variablen und calc gültig.

.colorful {
  --translation: 10;
  transform: 
    translateX(calc(var(--translation) * 1vw))
    translateY(calc(var(--translation) * 1vh));
  filter: hue-rotate(calc(var(--translation) * 4.5deg));

  will-change: transform, filter;
  transition: transform 5000ms ease-in-out, filter 5000ms linear;
}

.colorful.go {
  --translation: 80;
}

Siehe den Pen Single Custom Property, Multiple Calcs von Dan Wilson (@danwilson) auf CodePen.

Der einzelne Wert kann (anfangs 10, oder später auf 80… oder jede andere Zahl geändert) genommen und separat auf vw-Einheiten oder vh-Einheiten für eine Translation angewendet werden. Sie können ihn in deg für eine Rotation oder einen filter: hue-rotate() umwandeln.

Sie müssen die Einheiten bei der Variable nicht weglassen, aber solange Sie sie in Ihrer calc haben, können Sie es tun, und es eröffnet die Möglichkeit, sie anderswo auf mehr Arten zu verwenden. Animation Choreographie zur Verschiebung von Dauern und Verzögerungen kann durch Modifizierung des Basiswertes in verschiedenen Regeln erreicht werden. In diesem Beispiel möchten wir immer ms als Endeinheit haben, aber das Hauptresultat, das wir wollen, ist, dass unsere delay immer die Hälfte der duration der Animation ist. Dann können wir dies tun, indem wir nur unseren --duration-base modifizieren.

Siehe den Pen Delay based on Duration von Dan Wilson (@danwilson) auf CodePen.

Sogar Kubische Bézier-Kurven können über Custom Properties modifiziert werden. Im folgenden Beispiel gibt es mehrere gestapelte Boxen. Jede hat eine etwas kleinere Skalierung, und jede erhält einen kubischen Bézier-Multiplikator. Dieser Multiplikator wird individuell auf die vier Teile einer Basis-Kubischen Bézier-Kurve angewendet. Dies ermöglicht es jeder Box, eine Kubische Bézier-Kurve zu haben, die sich von den anderen unterscheidet, aber in Beziehung zu ihnen steht. Versuchen Sie, Boxen zu entfernen oder hinzuzufügen, um zu sehen, wie sie miteinander spielen. Drücken Sie irgendwo, um die Boxen zu diesem Punkt zu verschieben.

Siehe den Pen Spiral Trail… Kinda von Dan Wilson (@danwilson) auf CodePen.

JavaScript wird verwendet, um die Basis-Kubische Bézier-Kurve bei jedem Drücken zu randomisieren, sowie um den Multiplikator jeder Box einzustellen. Der Schlüsselteil des CSS ist jedoch

.x {
  transform: translateX(calc(var(--x) * 1px));
  /* baseline value, updated via JS on press */
  transition-timing-function: 
    cubic-bezier(
      var(--cubic1-1),
      var(--cubic1-2),
      var(--cubic1-3),
      var(--cubic1-4));
}
.advanced-calc .x {
  transition-timing-function: 
    cubic-bezier(
      calc(var(--cubic1-1) * var(--cubic1-change)),
      calc(var(--cubic1-2) * var(--cubic1-change)),
      calc(var(--cubic1-3) * var(--cubic1-change)),
      calc(var(--cubic1-4) * var(--cubic1-change)));
}

Wenn Sie dies in bestimmten Browsern sehen (oder sich fragen, warum dieses Beispiel eine .advanced-calc-Klasse hat), vermuten Sie vielleicht bereits, dass es ein Problem mit diesem Ansatz gibt. Tatsächlich gibt es eine wichtige Einschränkung… calc-Magie funktioniert nicht immer wie erwartet über die Browser hinweg. Ana Tudor diskutiert seit langem die Unterschiede in der Browserunterstützung für calc, und ich habe einen zusätzlichen Test für einige andere vereinfachte calc-Anwendungsfälle.

Die gute Nachricht: Alle Browser, die Custom Properties unterstützen, funktionieren größtenteils auch mit calc bei der Umwandlung in Einheiten wie px, vmin, rem und andere lineare Entfernungseinheiten innerhalb von Eigenschaften wie width und transform: translate().

Die nicht so gute Nachricht: Firefox und Edge haben oft Probleme mit anderen Einheitentypen, wie deg, ms und sogar % in einigen Kontexten. So würden die vorherigen filter: hue-rotate() und --rotation-Eigenschaften ignoriert werden. Sie haben sogar Probleme, calc(1 * 1) in bestimmten Fällen zu verstehen, so dass sogar das Einheitenlose (wie in rgb()) ein Problem sein kann.

Während alle Browser, die Custom Properties unterstützen, Variablen in unseren cubic-bezier zulassen, erlauben nicht alle calc auf irgendeiner Ebene. Ich glaube, diese calc-Probleme sind heute die Hauptbeschränkungen bei Custom Properties… und sie sind nicht einmal Teil von Custom Properties.

Es gibt Fehler in den Browsern für diese Probleme, und Sie können sie mit progressive enhancement umgehen. Die früheren Demos führen die cubic-bezier-Modifikationen nur durch, wenn sie wissen, dass sie diese handhaben können, ansonsten erhalten Sie die Basiswerte. Sie werden fälschlicherweise einen CSS @supports-Check bestehen, sodass ein JS Modernizr-ähnlicher Check benötigt wird.

function isAdvancedCalcSupported() {
  document.body.style.transitionTimingFunction = 'cubic-bezier(calc(1 * 1),1,1,1)';
  return getComputedStyle(document.body).transitionTimingFunction != 'ease';
  //if the browser does not understand it, the computed value will be the default value (in this case "ease")
}

Interaktion über JavaScript

Custom Properties sind großartig für das, was sie in CSS bieten, aber mehr Kraft wird freigeschaltet, wenn Sie über JavaScript kommunizieren. Wie in der cubic-bezier-Demo gezeigt, können wir mit JavaScript einen neuen Eigenschaftswert schreiben.

var element = document.documentElement;
element.style.setProperty('--name', value);

Dies setzt einen neuen Wert für eine global definierte Eigenschaft (in CSS in der :root-Regel definiert). Oder Sie können direkter vorgehen und einen neuen Wert für ein bestimmtes Element festlegen (und ihm somit die höchste Spezifität für dieses Element geben und die Variable für andere Elemente, die sie verwenden, unverändert lassen). Dies ist nützlich, wenn Sie einen Zustand verwalten und einen Stil basierend auf gegebenen Werten ändern müssen.

David Khourshid hat in dem Kontext von Observables leistungsstarke Möglichkeiten zur Interaktion mit Custom Properties über JS diskutiert, und sie passen sehr gut zusammen. Ob Sie Observables, React-Zustandsänderungen, bewährte Event-Listener oder eine andere Möglichkeit zur Ableitung von Wertänderungen verwenden möchten, eine breite Tür zur Kommunikation zwischen beiden ist nun offen.

Diese Kommunikation ist besonders wichtig für die CSS-Eigenschaften, die mehrere Werte annehmen. Wir haben lange Zeit das style-Objekt, um Stile aus JavaScript zu ändern, aber das kann kompliziert werden, sobald wir nur einen Teil eines langen Wertes ändern müssen. Wenn wir einen von zehn Hintergründen ändern müssen, die in einer background-Regel definiert sind, müssen wir wissen, welchen wir ändern und dann sicherstellen, dass wir die anderen neun unberührt lassen. Dies wird für transform-Regeln noch komplizierter, wenn Sie nur einen rotate() ändern und den aktuellen scale() unverändert lassen möchten. Mit Custom Properties können Sie JavaScript verwenden, um jeden einzeln zu ändern, was die Zustandsverwaltung der gesamten transform-Eigenschaft vereinfacht.

Siehe den Pen Dance of the Hexagons and Variables von Dan Wilson (@danwilson) auf CodePen.

Der einheitenlose Ansatz funktioniert auch hier gut. Ihre setProperty()-Aufrufe können rohe Zahlen an CSS übergeben, anstatt Einheiten anhängen zu müssen, was Ihr JavaScript in einigen Fällen vereinfachen kann.

Ist es Zeit, das zu nutzen?

Da Custom Properties jetzt in den neuesten Browsern von Mozilla, Google, Opera, Apple und Microsoft verfügbar sind, ist es definitiv eine gute Zeit, sie zu erkunden und damit zu experimentieren. Vieles von dem, was hier besprochen wird, kann jetzt mit sinnvollen Fallbacks verwendet werden. Die calc-Updates, die in einigen Browsern erforderlich sind, liegen weiter in der Zukunft, aber es gibt immer noch Zeiten, in denen Sie sie vernünftig einsetzen können. Zum Beispiel, wenn Sie an hybriden mobilen Apps arbeiten, die auf neuere iOS-, Android- oder Windows-Versionen beschränkt sind, haben Sie mehr Spielraum.

Custom Properties stellen eine große Ergänzung für CSS dar, und es kann einige Zeit dauern, bis Sie verstehen, wie alles funktioniert. Tauchen Sie ein, und dann stürzen Sie sich hinein, wenn es Ihnen gefällt.