Theming von Benutzeroberflächen bezieht sich auf die Fähigkeit, visuelle Stile konsistent zu ändern, was das "Look and Feel" einer Website definiert. Das Austauschen von Farbpaletten, z. B. für den Dark Mode oder auf andere Weise, ist ein gutes Beispiel. Aus Benutzersicht beinhaltet Theming die Änderung visueller Stile, sei es durch eine Benutzeroberfläche zur Auswahl eines Stilthemas oder durch die automatische Berücksichtigung der Farbschema-Präferenzen des Benutzers auf OS-Ebene. Aus Entwicklersicht sollten die für das Theming verwendeten Werkzeuge einfach zu bedienen sein und Themen zur Entwicklungszeit definieren, bevor sie zur Laufzeit angewendet werden.
Dieser Artikel beschreibt, wie man mit Mimcss, einer CSS-in-JS-Bibliothek, mit Klassenerbschaft – einer Methode, die für die meisten Entwickler intuitiv sein sollte, da es beim Theming normalerweise darum geht, CSS-Eigenschaftswerte zu überschreiben, und Vererbung perfekt für diese Überschreibungen ist – vorgehen kann.
Vollständige Offenlegung: Ich bin der Autor von Mimcss. Wenn Sie dies als schamlose Promotion betrachten, sind Sie nicht weit von der Wahrheit entfernt. Dennoch glaube ich wirklich, dass die Theming-Technik, die wir in diesem Artikel behandeln, einzigartig, intuitiv und erforschenswert ist.
Allgemeine Überlegungen zum Theming
Styling in Web-UIs wird durch Referenzierung von CSS-Entitäten (Klassen, IDs usw.) durch HTML-Elemente implementiert. Da sowohl HTML als auch CSS dynamisch sind, kann die Änderung der visuellen Darstellung durch eine der folgenden Methoden erreicht werden:
- Änderung des CSS-Selectors eines HTML-Elements, z. B. eines anderen Klassennamens oder einer anderen ID.
- Änderung des eigentlichen CSS-Stylings für dieses HTML-Element unter Beibehaltung des Selectors.
Je nach Kontext kann eine Methode effizienter sein als eine andere. Themes werden normalerweise durch eine begrenzte Anzahl von Stilentitäten definiert. Ja, Themes sind mehr als nur eine Sammlung von Farben und Schriftarten – sie können Abstände, Ränder, Layouts, Animationen usw. definieren. Es scheint jedoch, dass die Anzahl der von einem Theme definierten CSS-Entitäten geringer sein kann als die Anzahl der HTML-Elemente, die diese Entitäten referenzieren, insbesondere wenn wir über umfangreiche Widgets wie Tabellen, Bäume oder Code-Editoren sprechen. Mit dieser Annahme möchten wir bei einer Theme-Änderung lieber Stildefinitionen ersetzen, als die HTML-Elemente zu durchlaufen und (höchstwahrscheinlich) die Werte ihrer class-Attribute zu ändern.
Theming in reinem CSS
In regulärem CSS wird Theming unter anderem durch die Verwendung von alternativen Stylesheets unterstützt. Dies ermöglicht es Entwicklern, mehrere CSS-Dateien im <head> von HTML zu verknüpfen.
<link href="default.css" rel="stylesheet" type="text/css" title="Default Style">
<link href="fancy.css" rel="alternate stylesheet" type="text/css" title="Fancy">
<link href="basic.css" rel="alternate stylesheet" type="text/css" title="Basic">
Zu jedem Zeitpunkt kann nur eines der oben genannten Stylesheets aktiv sein, und Browser sollen eine Benutzeroberfläche bereitstellen, über die der Benutzer einen Theme-Namen auswählt, der aus den Werten des title-Attributs des <link>-Elements stammt. Die CSS-Regelnamen (d. h. Klassennamen) innerhalb der alternativen Stylesheets sollen identisch sein, wie z. B.
/* default.css */
.element {
color: #fff;
}
/* basic.css */
.element {
color: #333;
}
Auf diese Weise sind beim Aktivieren eines anderen Stylesheets durch den Browser keine HTML-Änderungen erforderlich. Der Browser berechnet die Stile (und das Layout) neu und malt die Seite basierend auf den "gewinnenden" Werten neu, wie von The Cascade bestimmt.
Alternative Stylesheets werden von Mainstream-Browsern leider nicht gut unterstützt und funktionieren in einigen davon nur mit speziellen Erweiterungen. Wie wir später sehen werden, baut Mimcss auf der Idee der alternativen Stylesheets auf, nutzt diese aber in einem reinen TypeScript-Framework.
Theming in CSS-in-JS
Es gibt zu viele CSS-in-JS-Bibliotheken, und es ist unmöglich, die Funktionsweise des Themings in CSS-in-JS in einem einzigen Beitrag vollständig zu behandeln, um ihm gerecht zu werden. Was CSS-in-JS-Bibliotheken angeht, die eng in React integriert sind (z. B. Styled Components), wird das Theming über die ThemeProvider-Komponente und die Context API oder über die withTheme Higher-Order-Komponente implementiert. In beiden Fällen führt die Änderung eines Themes zu einem erneuten Rendern. Bei CSS-in-JS-Bibliotheken, die Framework-agnostisch sind, wird Theming über proprietäre Mechanismen erreicht, wenn Theming überhaupt unterstützt wird.
Die Mehrheit der CSS-in-JS-Bibliotheken – sowohl React-spezifische als auch Framework-agnostische – konzentriert sich auf das "Scoping" von Stilregeln für Komponenten und ist daher hauptsächlich an der Erstellung eindeutiger Namen für CSS-Entitäten (z. B. CSS-Klassen) interessiert. In solchen Umgebungen bedeutet die Änderung eines Themes notwendigerweise eine Änderung des HTMLs. Dies steht im Gegensatz zum oben beschriebenen alternativen Stylesheets-Ansatz, bei dem Theming durch bloße Änderung der Stile erreicht wird.
Hier unterscheidet sich die Mimcss-Bibliothek. Sie versucht, das Beste aus beiden Theming-Welten zu vereinen. Einerseits folgt Mimcss dem alternativen Stylesheets-Ansatz, indem es mehrere Varianten von Stylesheets mit identisch benannten CSS-Entitäten definiert. Andererseits bietet es den objektorientierten Ansatz und ein leistungsfähiges TypeScript-Typsystem mit allen Vorteilen der dynamischen Programmierung und Typsicherheit von CSS-in-JS.
Theming in Mimcss
Mimcss gehört zu letzterer Gruppe von CSS-in-JS-Bibliotheken, da es Framework-agnostisch ist. Es wurde jedoch mit dem Hauptziel entwickelt, alles zu ermöglichen, was natives CSS in typsicherer Weise erlaubt, während die volle Leistung des TypeScript-Typsystems genutzt wird. Insbesondere verwendet Mimcss TypeScript-Klassen, um native CSS-Stylesheet-Dateien zu imitieren. So wie CSS-Dateien Regeln enthalten, enthalten die Mimcss Style Definition-Klassen Regeln.
Klassen eröffnen die Möglichkeit, Klassenerbschaft für die Implementierung von Theming zu nutzen. Die allgemeine Idee ist, dass eine Basisklasse CSS-Regeln deklariert, die von den Themes verwendet werden, während abgeleitete Klassen unterschiedliche Stil-Eigenschaftswerte für diese Regeln bereitstellen. Dies ähnelt stark dem nativen alternativen Stylesheets-Ansatz: Aktivieren Sie eine andere Theme-Klasse und ohne Änderungen am HTML-Code ändern sich die Stile.
Aber zunächst werfen wir einen kurzen Blick darauf, wie Stile in Mimcss definiert werden.
Mimcss-Grundlagen
Stylesheets in Mimcss werden als Style Definition-Klassen modelliert, die CSS-Regeln als ihre Eigenschaften definieren. Zum Beispiel
import * as css from "mimcss"
class MyStyles extends css.StyleDefinition
{
significant = this.$class({
color: "orange",
fontStyle: "italic"
})
critical = this.$id({
color: "red",
fontWeight: 700
})
}
Die Mimcss-Syntax versucht, so nah wie möglich an regulärem CSS zu sein. Sie ist natürlich etwas ausführlicher; schließlich ist es reines TypeScript, das keine Plug-ins oder Vorverarbeitung erfordert. Aber es folgt immer noch den regulären CSS-Mustern: Für jede Regel gibt es den Regelsnamen (z. B. significant), die Art der Regel (z. B. $class) und die Stileigenschaften, die die Regel enthält.
Zusätzlich zu CSS-Klassen und -IDs können Stildefinitionseigenschaften andere CSS-Regeln definieren, z. B. Tags, Keyframes, benutzerdefinierte CSS-Eigenschaften, Stilregeln mit beliebigen Selektoren, Media-, @font-face-, Counter-Regeln und so weiter. Mimcss unterstützt auch verschachtelte Regeln, einschließlich solcher mit Pseudoklassen und Pseudoelementen.
Nachdem eine Stildefinitionsklasse definiert wurde, sollten die Stile aktiviert werden.
let styles = css.activate(MyStyles);
Das Aktivieren von Stilen erstellt eine Instanz der Stildefinitionsklasse und schreibt die CSS-Regeln in das DOM. Um die Stile zu verwenden, referenzieren wir die Eigenschaften der Instanz in unserem HTML-Rendering-Code.
render()
{
return <div>
<p className={styles.significant.name}>
This is a significant paragraph.
</p>
<p id={styles.critical.name}>
This is a critical paragraph.
</p>
</div>
}
Wir verwenden styles.significant.name als CSS-Klassennamen. Beachten Sie, dass die Eigenschaft styles.significant keine Zeichenkette, sondern ein Objekt mit der Eigenschaft name ist und den CSS-Klassennamen enthält. Die Eigenschaft selbst bietet auch Zugriff auf die CSS-Objektmodellregel, die eine direkte Regelmanipulation ermöglicht. Dies liegt jedoch außerhalb des Rahmens dieses Artikels (obwohl Louis Lazaris einen großartigen Artikel dazu hat).
Wenn die Stile nicht mehr benötigt werden, können sie deaktiviert werden, wodurch sie aus dem DOM entfernt werden.
css.deactivate(styles);
Die CSS-Klassen- und ID-Namen werden von Mimcss eindeutig generiert. Der Generierungsmechanismus unterscheidet sich in den Entwicklungs- und Produktionsversionen der Bibliothek. Zum Beispiel wird für die CSS-Klasse significant der Name in der Entwicklungsversion als MyStyles_significant und in der Produktionsversion als etwas wie n2 generiert. Die Namen werden generiert, wenn die Stildefinitionsklasse zum ersten Mal aktiviert wird, und sie bleiben gleich, egal wie oft die Klasse aktiviert und deaktiviert wird. Wie die Namen generiert werden, hängt davon ab, in welcher Klasse sie zuerst deklariert wurden, und das wird sehr wichtig, wenn wir beginnen, Stildefinitionen zu erben.
Vererbung von Stildefinitionen
Betrachten wir ein einfaches Beispiel und sehen, was Mimcss bei vorhandener Vererbung tut.
class Base extends css.StyleDefinition
{
pad4 = this.$class({ padding: 4 })
}
class Derived extends Base
{
pad8 = this.$class({ padding: 8 })
}
let derived = css.activate(Derived);
Nichts Überraschendes passiert, wenn wir die Klasse Derived aktivieren: Die Variable derived bietet Zugriff auf die CSS-Klassen pad4 und pad8. Mimcss generiert für jede dieser Eigenschaften einen eindeutigen CSS-Klassennamen. Die Namen der Klassen sind Base_pad4 und Derived_pad8 in der Entwicklungsversion der Bibliothek.
Interessante Dinge passieren, wenn die Klasse Derived eine Eigenschaft der Basisklasse überschreibt.
class Base extends css.StyleDefinition
{
pad = this.$class({ padding: 4 })
}
class Derived extends Base
{
pad = this.$class({ padding: 8 })
}
let derived = css.activate(Derived);
Es gibt einen einzigen Namen für die Variable derived.pad.name. Der Name ist Base_pad; der Stil ist jedoch { padding: 8px }. Das heißt, der Name wird unter Verwendung des Namens der Basisklasse generiert, während der Stil aus der abgeleiteten Klasse übernommen wird.
Versuchen wir eine weitere Stildefinitionsklasse, die von derselben Base-Klasse abgeleitet ist.
class AnotherDerived extends Base
{
pad = this.$class({ padding: 16 })
}
let anotherDerived = css.activate(AnotherDerived);
Wie erwartet hat anotherDerived.pad.name den Wert Base_pad und der Stil ist { padding: 16px }. Daher verwenden, egal wie viele verschiedene abgeleitete Klassen wir haben, alle denselben Namen für die geerbten Eigenschaften, aber ihnen werden unterschiedliche Stile zugewiesen. Dies ist die Kernfunktion von Mimcss, die es uns ermöglicht, die Vererbung von Stildefinitionen für das Theming zu nutzen.
Themen in Mimcss erstellen
Die Hauptidee des Themings in Mimcss ist, eine Theme-Deklarationsklasse zu haben, die mehrere CSS-Regeln deklariert, und mehrere Implementierungsklassen zu haben, die von der Deklaration abgeleitet sind und diese Regeln überschreiben, indem sie tatsächliche Stilwerte bereitstellen. Wenn wir CSS-Klassennamen sowie andere benannte CSS-Entitäten in unserem Code benötigen, können wir die Eigenschaften der Theme-Deklarationsklasse verwenden. Dann können wir entweder diese oder jene Implementierungsklasse aktivieren und schon können wir das Styling unserer Anwendung mit sehr wenig Code komplett ändern.
Betrachten wir ein sehr einfaches Beispiel, das den gesamten Ansatz des Themings in Mimcss gut demonstriert: Ein Theme definiert einfach die Form und den Stil des Rahmens eines Elements.
Zuerst müssen wir die Theme-Deklarationsklasse erstellen. Theme-Deklarationen sind Klassen, die von der Klasse ThemeDefinition abgeleitet sind, die selbst von der Klasse StyleDefinition abgeleitet ist (es gibt eine Erklärung, warum wir die Klasse ThemeDefinition benötigen und warum Themes nicht direkt von der Klasse StyleDefinition abgeleitet werden sollten, aber das ist ein Thema für einen anderen Tag).
class BorderTheme extends css.ThemeDefinition
{
borderShape = this.$class()
}
Die Klasse BorderTheme definiert eine einzige CSS-Klasse, borderShape. Beachten Sie, dass wir dafür keine Stile angegeben haben. Wir verwenden diese Klasse nur, um den Typ der Eigenschaft borderShape zu definieren, und lassen Mimcss einen eindeutigen Namen dafür erstellen. In gewisser Weise ähnelt dies der Methodendeklaration in einer Schnittstelle – sie deklariert ihre Signatur, die von den abgeleiteten Klassen implementiert werden sollte.
Lassen Sie uns nun zwei tatsächliche Themes definieren – unter Verwendung der Klassen SquareBorderTheme und RoundBorderTheme –, die von der Klasse BorderTheme abgeleitet sind und die Eigenschaft borderShape überschreiben, indem sie verschiedene Stilparameter angeben.
class SquareBorderTheme extends BorderTheme
{
borderShape = this.$class({
border: ["thin", "solid", "green"],
borderInlineStartWidth: "thick"
})
}
class RoundBorderTheme extends BorderTheme
{
borderShape = this.$class({
border: ["medium", "solid", "blue"],
borderRadius: 8 // Mimcss will convert 8 to 8px
})
}
TypeScript stellt sicher, dass abgeleitete Klassen eine Eigenschaft nur mit demselben Typ überschreiben können, der in der Basisklasse deklariert wurde, was in unserem Fall ein interner Mimcss-Typ zur Definition von CSS-Klassen ist. Das bedeutet, dass Entwickler die Eigenschaft borderShape nicht verwenden können, um versehentlich eine andere CSS-Regel zu deklarieren, da dies zu einem Kompilierungsfehler führt.
Wir können nun eines der Themes als Standard-Theme aktivieren.
let theme: BorderTheme = css.activate(SquareBorderTheme);
Wenn Mimcss eine Stildefinitionsklasse zum ersten Mal aktiviert, generiert es eindeutige Namen für alle in der Klasse definierten CSS-Entitäten. Wie wir bereits gesehen haben, wird der Name, der für die Eigenschaft borderShape generiert wird, einmal generiert und wird wiederverwendet, wenn andere Klassen, die von der Klasse BorderTheme abgeleitet sind, aktiviert werden.
Die Funktion activate gibt eine Instanz der aktivierten Klasse zurück, die wir in der Variable theme vom Typ BorderTheme speichern. Diese Variable sagt dem TypeScript-Compiler, dass er Zugriff auf alle Eigenschaften von BorderTheme hat. Dies ermöglicht es uns, den folgenden Rendering-Code für eine fiktive Komponente zu schreiben.
render()
{
return <div>
<input type="text" className={theme.borderShape.name} />
</div>
}
Alles, was noch zu tun ist, ist der Code, der es dem Benutzer ermöglicht, eines der beiden Themes auszuwählen und zu aktivieren.
onToggleTheme()
{
if (theme instanceof SquareBorderTheme)
theme = css.activate(RoundBorderTheme);
else
theme = css.activate(SquareBorderTheme);
}
Beachten Sie, dass wir das alte Theme nicht deaktivieren mussten. Eines der Merkmale der Klasse ThemeDefinition (im Gegensatz zur Klasse StyleDefintion) ist, dass sie für jede Theme-Deklarationsklasse nur ein einziges Theme gleichzeitig zulässt. Das heißt, in unserem Fall kann entweder RoundBorderTheme oder SquareBorderTheme aktiv sein, aber niemals beide. Natürlich können für mehrere Theme-Hierarchien mehrere Themes gleichzeitig aktiv sein. Das heißt, wenn wir eine weitere Hierarchie mit der Deklarationsklasse ColorTheme und den abgeleiteten Klassen DarkTheme und LightTheme haben, kann eine einzelne Klasse, die von ColorTheme abgeleitet ist, gleichzeitig mit einer einzelnen Klasse, die von BorderTheme abgeleitet ist, aktiv sein. DarkTheme und LightTheme können jedoch nicht gleichzeitig aktiv sein.
Mimcss-Themes referenzieren
Im gerade betrachteten Beispiel haben wir ein Theme-Objekt direkt verwendet, aber Themes definieren häufig Elemente wie Farben, Größen und Schriftarten, die von anderen Stildefinitionen referenziert werden können. Dies ist besonders nützlich, um den Code, der Themes definiert, von dem Code zu trennen, der Stile für eine Komponente definiert, die nur die von dem aktuell aktiven Theme definierten Elemente verwenden möchte.
CSS-Custom-Properties eignen sich perfekt zum Deklarieren von Elementen, aus denen Stile aufgebaut werden können. Definieren wir also zwei benutzerdefinierte Eigenschaften in unseren Themes: eine für die Vordergrundfarbe und eine für die Hintergrundfarbe. Wir können auch eine einfache Komponente erstellen und eine separate Stildefinitionsklasse dafür definieren. Hier ist, wie wir die Theme-Deklarationsklasse definieren.
class ColorTheme extends css.ThemeDefinition
{
bgColor = this.$var( "color")
frColor = this.$var( "color")
}
Die Methode $var definiert eine CSS-Custom-Property. Der erste Parameter gibt den Namen der CSS-Stil-Eigenschaft an, die akzeptable Eigenschaftswerte bestimmt. Beachten Sie, dass wir hier keine tatsächlichen Werte angeben; in der Deklarationsklasse wollen wir nur, dass Mimcss eindeutige Namen für die benutzerdefinierten CSS-Eigenschaften (z. B. --n13) erstellt, während die Werte in den Theme-Implementierungsklassen angegeben werden, was wir als nächstes tun.
class LightTheme extends ColorTheme
{
bgColor = this.$var( "color", "white")
frColor = this.$var( "color", "black")
}
class DarkTheme extendsBorderTheme
{
bgColor = this.$var( "color", "black")
frColor = this.$var( "color", "white")
}
Dank des Mimcss- (und natürlich des TypeScript-) Typsystems können Entwickler nicht versehentlich z. B. die bgColor-Eigenschaft mit einem anderen Typ wiederverwenden; sie können auch keine Werte angeben, die für einen Farbtyp nicht akzeptabel sind. Andernfalls würde sofort ein Kompilierungsfehler auftreten, der Entwickler einige Zyklen ersparen kann (eines der erklärten Ziele von Mimcss).
Definieren wir Stile für unsere Komponente, indem wir die benutzerdefinierten CSS-Eigenschaften des Themes referenzieren.
class MyStyles extends css.StyleDefinition
{
theme = this.$use(ColorTheme)
container = this.$class({
color: this.theme.fgColor,
backgroundColor: this.theme.bgColor,
})
}
Die Stildefinitionsklasse MyStyles referenziert die Klasse ColorTheme, indem sie die Mimcss-Methode $use aufruft. Dies gibt eine Instanz der Klasse ColorTheme zurück, über die alle ihre Eigenschaften zugänglich sind und zur Zuweisung von Werten zu CSS-Eigenschaften verwendet werden können.
Wir müssen die var()-Funktionsaufrufung nicht schreiben, da Mimcss dies bereits tut, wenn die $var-Eigenschaft referenziert wird. Im Wesentlichen erstellt die CSS-Klasse für die Eigenschaft container die folgende CSS-Regel (natürlich mit eindeutig generierten Namen).
.container {
color: var(--fgColor);
backgroundColor: var(--bgColor);
}
Jetzt können wir unsere Komponente (im Pseudo-React-Stil) definieren.
class MyComponent extends Component
{
private styles = css.activate(MyStyles);
componentWillUnmount()
{
css.deactivate(this.styles);
}
render()
{
return <div className={this.styles.container.name}>
This area will change colors depending on a selected theme.
</div>
}
}
Beachten Sie eine wichtige Sache im obigen Code: Unsere Komponente ist vollständig von den Klassen entkoppelt, die tatsächliche Themes implementieren. Die einzige Klasse, die unsere Komponente kennen muss, ist die Theme-Deklarationsklasse ColorTheme. Dies öffnet die Tür, die Erstellung von Themes einfach zu "externalisieren" – sie können von Drittanbietern erstellt und als reguläre JavaScript-Pakete geliefert werden. Solange sie von der Klasse ColorTheme abgeleitet sind, können sie aktiviert werden und unsere Komponente spiegelt ihre Werte wider.
Stellen Sie sich vor, Sie erstellen eine Theme-Deklarationsklasse für z. B. Material Design-Stile zusammen mit mehreren Theme-Klassen, die von dieser Klasse abgeleitet sind. Die einzige Einschränkung ist, dass wir, da wir ein bestehendes System verwenden, die tatsächlichen Namen der CSS-Eigenschaften nicht von Mimcss generieren lassen können – sie müssen die exakten Namen sein, die das Material Design-System verwendet (z. B. --mdc-theme--primary). Glücklicherweise bietet Mimcss für alle benannten CSS-Entitäten eine Möglichkeit, seinen internen Namensgenerierungsmechanismus zu überschreiben und einen explizit angegebenen Namen zu verwenden. So kann dies mit Material Design CSS-Eigenschaften geschehen.
class MaterialDesignThemeBase extends css.ThemeDefinition
{
primary = this.$var( "color", undefined, "mdc-theme--primary")
onPrimary = this.$var( "color", undefined, "mdc-theme--on-primary")
// ...
}
Der dritte Parameter im Aufruf von $var ist der Name, der der CSS-Custom-Property zugewiesen wird. Der zweite Parameter wird auf undefined gesetzt, was bedeutet, dass wir keinen Wert für die Eigenschaft angeben, da es sich um eine Theme-Deklaration und nicht um eine konkrete Theme-Implementierung handelt.
Die Implementierungsklassen müssen sich nicht um die Angabe der korrekten Namen kümmern, da alle Namenszuweisungen auf der Theme-Deklarationsklasse basieren.
class MyMaterialDesignTheme extends MaterialDesignThemeBase
{
primary = this.$var( "color", "lightslategray")
onPrimary = this.$var( "color", "navy")
// ...
}
Mehrere Themes auf einer Seite
Wie bereits erwähnt, kann nur eine einzige Theme-Implementierung aus den von derselben Theme-Deklarationsklasse abgeleiteten Themes aktiv sein. Der Grund dafür ist, dass unterschiedliche Theme-Implementierungen unterschiedliche Werte für die CSS-Regeln mit denselben Namen definieren. Wenn also mehrere Theme-Implementierungen gleichzeitig aktiv sein könnten, hätten wir mehrere Definitionen von identisch benannten CSS-Regeln. Dies ist natürlich ein Rezept für Desaster.
Normalerweise ist es kein Problem, nur ein Theme gleichzeitig aktiv zu haben – das ist wahrscheinlich in den meisten Fällen erwünscht. Themes definieren normalerweise das allgemeine Erscheinungsbild der gesamten Seite, und es besteht keine Notwendigkeit, dass verschiedene Seitenabschnitte unterschiedliche Themes verwenden. Was aber, wenn wir uns in der seltenen Situation befinden, dass wir unterschiedliche Themes auf verschiedene Teile unserer Seite anwenden müssen? Was ist zum Beispiel, wenn wir dem Benutzer erlauben möchten, zwei Modi nebeneinander zu vergleichen, bevor er ein helles oder dunkles Theme wählt?
Die Lösung basiert auf der Tatsache, dass benutzerdefinierte CSS-Eigenschaften unter CSS-Regeln neu definiert werden können. Da Theme-Definitionsklassen normalerweise viele benutzerdefinierte CSS-Eigenschaften enthalten, bietet Mimcss eine einfache Möglichkeit, ihre Werte aus verschiedenen Themes unter verschiedenen CSS-Regeln zu verwenden.
Betrachten wir ein Beispiel, bei dem wir zwei Elemente mit zwei verschiedenen Themes auf derselben Seite anzeigen müssen. Die Idee ist, eine Stildefinitionsklasse für unsere Komponente zu erstellen, damit wir den folgenden Rendering-Code schreiben können.
public render()
{
return <div>
<div className={this.styles.top.name}>
This should be black text on white background
</div>
<div className={this.styles.bottom.name}>
This should be white text on black background
</div>
</div>
}
Wir müssen die CSS-Klassen top und bottom definieren, damit wir die benutzerdefinierten Eigenschaften unter jeder von ihnen neu definieren und Werte aus verschiedenen Themes übernehmen. Wir wollen im Wesentlichen folgendes CSS haben.
.block {
backgroundColor: var(--bgColor);
color: var(--fgColor);
}
.block.top {
--bgColor: while;
--fgColor: black;
}
.block.bottom {
--bgColor: black;
--fgColor: white;
}
Wir verwenden die Klasse block aus Optimierungsgründen und um zu zeigen, wie Mimcss das Erben von CSS-Stilen behandelt, aber dies ist optional.
So wird dies in Mimcss gemacht.
class MyStyles extends css.StyleDefinition
{
theme = this.$use(ColorTheme)
block = this.$class({
backgroundColor: this.theme.bgColor,
color: this.theme.fgColor
})
top = this.$class({
"++": this.block,
"--": [LightTheme],
})
bottom = this.$class({
"++": this.block,
"--": [DarkTheme],
})
}
Genau wie zuvor referenzieren wir unsere Deklarationsklasse ColorTheme. Dann definieren wir eine Hilfs-CSS-Klasse block, die die Vordergrund- und Hintergrundfarben mit den benutzerdefinierten CSS-Eigenschaften des Themes setzt. Dann definieren wir die Klassen top und bottom und verwenden die Eigenschaft "++", um anzuzeigen, dass sie von der Klasse block erben. Mimcss unterstützt mehrere Methoden der Stilvererbung; die Eigenschaft "++" hängt einfach den Namen der referenzierten Klasse an unseren Klassennamen an. Das heißt, der von styles.top.name zurückgegebene Wert ist "top block", wobei wir die beiden CSS-Klassen kombinieren (die tatsächlichen Namen sind zufällig generiert, also wäre es etwas wie "n153 n459").
Dann verwenden wir die Eigenschaft "--", um Werte der benutzerdefinierten CSS-Variablen festzulegen. Mimcss unterstützt mehrere Methoden zur Neudefinition von benutzerdefinierten CSS-Eigenschaften in einem Regelsatz; in unserem Fall referenzieren wir einfach eine entsprechende Theme-Definitionsklasse. Dies bewirkt, dass Mimcss alle in der Theme-Klasse gefundenen benutzerdefinierten CSS-Eigenschaften mit ihren entsprechenden Werten neu definiert.
Was denkst du?
Theming in Mimcss basiert absichtlich auf der Vererbung von Stildefinitionen. Wir haben genau untersucht, wie dies funktioniert, und dabei das Beste aus beiden Welten des Themings erhalten: die Möglichkeit, alternative Stylesheets zu verwenden, zusammen mit der Möglichkeit, CSS-Eigenschaftswerte mithilfe eines objektorientierten Ansatzes auszutauschen.
Zur Laufzeit wendet Mimcss ein Theme an, ohne das HTML überhaupt zu ändern. Zur Build-Zeit nutzt Mimcss die bewährte und einfach zu bedienende Technik der Klassenerbschaft. Bitte lesen Sie die Mimcss-Dokumentation für eine viel tiefere Einsicht in die hier behandelten Themen. Sie können auch den Mimcss Playground besuchen, wo Sie eine Reihe von Beispielen erkunden und Ihren eigenen Code einfach ausprobieren können.
Und sagen Sie mir natürlich, was Sie von diesem Ansatz halten! Dies war meine bevorzugte Lösung für das Theming, und ich möchte sie basierend auf dem Feedback von Entwicklern wie Ihnen weiter stärken.
Ich verwende TypeScript seit einigen Jahren mit anderen CSS-in-JS-Bibliotheken, und die Erfahrung war fantastisch, insbesondere beim Übergeben von Design-Tokens.
Ein Kernkonzept von Komponenten ist, dass CSS, HTML und jedes JS, das die Anzeigelogik steuert, dasselbe Anliegen darstellen. Das willkürliche Styling eines Komponententenbaums von oben ist eine Verletzung dieser Anliegen. Eine Komponente kann sich ändern und versehentlich ihre Beziehung zum übergeordneten Theme ungültig machen. (Diese implizite Beziehung macht die Kaskade so zerbrechlich.)
Eine der Herausforderungen bei gescoped Komponenten ist das responsive Design, z. B. das Ändern der Reihenfolge benannter Grid-Bereiche basierend auf der Bildschirmbreite, da übergeordnete Elemente nicht in ihre untergeordneten Elemente eingreifen und Änderungen über Klassenselektoren vornehmen können, und untergeordnete Komponenten keinen Begriff von ihrer Position auf der Seite haben. (Sie können schummeln, indem Sie Hooks von der untergeordneten Komponente bereitstellen, aber das kann leicht missbraucht werden. Shadow DOM verhindert dies weitgehend per Design.) Custom Properties mildern dieses Problem, indem sie die Angriffsfläche einer Stiländerung auf einen einzigen Eigenschaftswert anstatt auf eine gesamte Klasse reduzieren. Das Ändern des Werts einer benutzerdefinierten Eigenschaft über eine Media-Abfrage wird sich durch Ihre Komponenten kaskadieren, die die benutzerdefinierte Eigenschaft hoffentlich wie vorgesehen verwenden.
Das Problem mit benutzerdefinierten Eigenschaften ist, dass sie genauso fragil sind wie der Rest der Kaskade: Sie können mit beliebigen Werten überschrieben werden, unterliegen Namenskollisionen und es gibt keine Möglichkeit, sie zu validieren, bevor sie verwendet werden. Außerdem gibt es einen Performance-Nachteil, da die Auflösung von benutzerdefinierten Eigenschaften zur Laufzeit erfolgen muss. Wenn Schriftarten und Farben sich nicht zur Laufzeit ändern müssen (aktualisieren Sie einfach die Seite, wenn der Benutzer das Design ändert), gibt es keinen Grund, sie an benutzerdefinierte Eigenschaften zu binden.
Wenn Sie eine CSS in JS-Lösung verwenden, kann Ihr JS Variablen viel expliziter verfolgen und sie validieren, bevor sie von der Komponente verwendet werden. Sie können beispielsweise sicherstellen, dass eine Farbvariable sowohl existiert als auch einem erwarteten Wert entspricht, bevor die Komponente sie tatsächlich verwendet. Mit SASS können Sie all dies während der Kompilierung tun, sodass zur Laufzeit keine Leistungseinbußen entstehen, und die Komponente einfach mit einer anderen CSS-Klassenzuweisung rendern.
Dennoch ist statisch typisiertes CSS erstaunlich. Ich kann nicht genug betonen, wie einfach es ist, CSS zu refaktorisieren und wie flüssig das Schreiben von CSS ist, wenn Ihre IDE Dinge wie die verfügbaren Breakpoints automatisch vervollständigt.