Eines der coolsten Dinge, mit denen ich in den letzten Jahren herumgespielt habe, ist die CSS Paint API. Ich liebe sie. Ich habe einen Vortrag darüber gehalten und eine kleine Galerie meiner eigenen Paint-Worklets erstellt. Das andere coole Ding ist die prefers-color-scheme Media Query und wie man sie verwenden kann, um sich an die Präferenzen eines Benutzers für helle oder dunkle Modi anzupassen.
Vor kurzem habe ich herausgefunden, dass ich beide dieser *wirklich coolen Dinge* mit CSS-benutzerdefinierten Eigenschaften so kombinieren kann, dass das Erscheinungsbild eines Paint-Worklets an das bevorzugte Farbschema des Benutzers angepasst werden kann!
Die Bühne bereiten
Ich war längst überfällig für eine Website-Überholung und habe mich für ein Final Fantasy II-Thema entschieden. Meine erste Aufgabe war es, ein Paint-Worklet zu erstellen, das eine zufällig generierte Landschaft im Final Fantasy-Stil war, die ich overworld.js nannte.

Sie könnte etwas mehr Aufputz vertragen – und das steht sicherlich auf dem Programm –, aber das hier ist ein verdammt guter Anfang!
Nachdem ich das Paint-Worklet fertiggestellt hatte, habe ich mich anderen Teilen der Website zugewandt, wie zum Beispiel einem Theme-Switcher für helle und dunkle Modi. Damals erkannte ich, dass sich das Paint-Worklet nicht an diese Präferenzen anpasste. Das könnte normalerweise eine riesige Qual sein, aber mit CSS-benutzerdefinierten Eigenschaften erkannte ich, dass ich die Rendering-Logik des Paint-Worklets mit relativer Leichtigkeit an das bevorzugte Farbschema eines Benutzers anpassen konnte!
Einrichtung der benutzerdefinierten Eigenschaften für das Paint-Worklet
Der aktuelle Stand von CSS ist ziemlich großartig, und CSS-benutzerdefinierte Eigenschaften sind ein Beispiel für diese Großartigkeit. Um sicherzustellen, dass sowohl die Paint API als auch die Funktionen für benutzerdefinierte Eigenschaften unterstützt werden, führen Sie einen kleinen Feature-Check wie diesen durch.
const paintAPISupported = "registerProperty" in window.CSS && "paintWorklet" in window.CSS`
Der erste Schritt ist die Definition Ihrer benutzerdefinierten Eigenschaften, was die Methode CSS.registerProperty beinhaltet. Das sieht ungefähr so aus.
CSS.registerProperty({
name, // The name of the property
syntax, // The syntax (e.g., <number>, <color>, etc.)
inherits, // Whether the value can be inherited by other properties
initialValue // The default value
});
Benutzerdefinierte Eigenschaften sind der beste Teil der Verwendung der Paint API, da diese Werte in CSS angegeben werden, aber im Kontext des Paint-Worklets lesbar sind. Dies gibt Entwicklern eine äußerst praktische Möglichkeit, die Darstellung eines Paint-Worklets zu steuern – *vollständig in CSS*.
Für das overworld.js Paint-Worklet werden die benutzerdefinierten Eigenschaften verwendet, um die Farben für verschiedene Teile der zufällig generierten Landschaft zu definieren – das Gras und die Bäume, den Fluss, die Flussufer und so weiter. Diese Standardfarben sind für das helle Farbschema.
Ich registriere diese Eigenschaften, indem ich alles in einem Objekt einrichte, das ich mit Object.entries aufrufe und dann über die Einträge iteriere. Im Fall meines overworld.js Paint-Worklets sah das so aus.
// Specify the paint worklet's custom properties
const properties = {
"--overworld-grass-green-color": {
syntax: "<color>",
initialValue: "#58ab1d"
},
"--overworld-dark-rock-color": {
syntax: "<color>",
initialValue: "#a15d14"
},
"--overworld-light-rock-color": {
syntax: "<color>",
initialValue: "#eba640"
},
"--overworld-river-blue-color": {
syntax: "<color>",
initialValue: "#75b9fd"
},
"--overworld-light-river-blue-color": {
syntax: "<color>",
initialValue: "#c8e3fe"
}
};
// Register the properties
Object.entries(properties).forEach(([name, { syntax, initialValue }]) => {
CSS.registerProperty({
name,
syntax,
inherits: false,
initialValue
});
});
// Register the paint worklet
CSS.paintWorklet.addModule("/worklets/overworld.js");
Da jede Eigenschaft einen Anfangswert setzt, müssen Sie keine benutzerdefinierten Eigenschaften angeben, wenn Sie das Paint-Worklet später aufrufen. Da die Standardwerte für diese Eigenschaften jedoch überschrieben werden können, können sie angepasst werden, wenn Benutzer eine Präferenz für ein Farbschema äußern.
Anpassung an das bevorzugte Farbschema eines Benutzers
Die Website-Überarbeitung, an der ich arbeite, verfügt über ein Einstellungsmenü, das über die Hauptnavigation der Website zugänglich ist. Von dort aus können Benutzer eine Reihe von Präferenzen einstellen, einschließlich ihres bevorzugten Farbschemas.
Die Farbschema-Einstellung wechselt durch drei Optionen.
- System
- Hell
- Dunkel
„System“ entspricht standardmäßig dem, was der Benutzer in den Einstellungen seines Betriebssystems angegeben hat. Die letzten beiden Optionen überschreiben die Einstellung auf Betriebssystemebene des Benutzers, indem sie eine light oder dark Klasse auf das <html> Element setzen, aber in Abwesenheit einer expliziten Einstellung stützt sich die „System“-Einstellung auf das, was in den prefers-color-scheme Media Queries angegeben ist.
Der Dreh- und Angelpunkt für diese Überschreibung hängt von CSS-Variablen ab.
/* Kicks in if the user's site-level setting is dark mode */
html.dark {
/* (I'm so good at naming colors) */
--pink: #cb86fc;
--firion-red: #bb4135;
--firion-blue: #5357fb;
--grass-green: #3a6b1a;
--light-rock: #ce9141;
--dark-rock: #784517;
--river-blue: #69a3dc;
--light-river-blue: #b1c7dd;
--menu-blue: #1c1f82;
--black: #000;
--white: #dedede;
--true-black: #000;
--grey: #959595;
}
/* Kicks in if the user's system setting is dark mode */
@media screen and (prefers-color-scheme: dark) {
html {
--pink: #cb86fc;
--firion-red: #bb4135;
--firion-blue: #5357fb;
--grass-green: #3a6b1a;
--light-rock: #ce9141;
--dark-rock: #784517;
--river-blue: #69a3dc;
--light-river-blue: #b1c7dd;
--menu-blue: #1c1f82;
--black: #000;
--white: #dedede;
--true-black: #000;
--grey: #959595;
}
}
/* Kicks in if the user's site-level setting is light mode */
html.light {
--pink: #fd7ed0;
--firion-red: #bb4135;
--firion-blue: #5357fb;
--grass-green: #58ab1d;
--dark-rock: #a15d14;
--light-rock: #eba640;
--river-blue: #75b9fd;
--light-river-blue: #c8e3fe;
--menu-blue: #252aad;
--black: #0d1b2a;
--white: #fff;
--true-black: #000;
--grey: #959595;
}
/* Kicks in if the user's system setting is light mode */
@media screen and (prefers-color-scheme: light) {
html {
--pink: #fd7ed0;
--firion-red: #bb4135;
--firion-blue: #5357fb;
--grass-green: #58ab1d;
--dark-rock: #a15d14;
--light-rock: #eba640;
--river-blue: #75b9fd;
--light-river-blue: #c8e3fe;
--menu-blue: #252aad;
--black: #0d1b2a;
--white: #fff;
--true-black: #000;
--grey: #959595;
}
}
Es ist repetitiv – und ich bin sicher, jemand da draußen kennt einen besseren Weg –, aber es erledigt die Aufgabe. Unabhängig von der expliziten Site-weiten Präferenz des Benutzers oder seiner zugrunde liegenden Systempräferenz wird die Seite zuverlässig im entsprechenden Farbschema gerendert.
Setzen benutzerdefinierter Eigenschaften auf dem Paint-Worklet
Wenn die Paint API unterstützt wird, wendet ein kleiner Inline-Script im <head> des Dokuments eine paint-api Klasse auf das <html> Element an.
/* The main content backdrop rendered at a max-width of 64rem.
We don't want to waste CPU time if users can't see the
background behind the content area, so we only allow it to
render when the screen is 64rem (1024px) or wider. */
@media screen and (min-width: 64rem) {
.paint-api .backdrop {
background-image: paint(overworld);
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
/* These oh-so-well-chosen property names refer to the
theme-driven CSS variables that vary according to
the user's preferred color scheme! */
--overworld-grass-green-color: var(--grass-green);
--overworld-dark-rock-color: var(--dark-rock);
--overworld-light-rock-color: var(--light-rock);
--overworld-river-blue-color: var(--river-blue);
--overworld-light-river-blue-color: var(--light-river-blue);
}
}
Hier gibt es sicher einige Besonderheiten. Aus einem Grund, der später vielleicht oder vielleicht auch nicht zutrifft – aber zumindest zum Zeitpunkt des Schreibens der Fall ist –, kann man die Ausgabe eines Paint-Worklets nicht direkt auf dem <body> Element rendern.
Außerdem möchte ich nicht, dass der *gesamte* Hintergrund der Seite mit zufällig generierten (und damit potenziell teuren) Grafiken gefüllt ist, da einige Seiten ziemlich lang sein können. Um dies zu umgehen, rendert das Paint-Worklet in einem Element, das eine feste Positionierung verwendet, dem Benutzer beim Scrollen folgt und den gesamten Viewport einnimmt.
Abgesehen von allen Eigenheiten liegt die Magie darin, dass die benutzerdefinierten Eigenschaften für das Paint-Worklet auf der Farbschema-Präferenz des Benutzers (System- oder Site-Ebene) basieren, da die CSS-Variablen mit dieser Präferenz übereinstimmen. Im Fall des overworld Paint-Worklets bedeutet dies, dass ich seine Ausgabe an das bevorzugte Farbschema des Benutzers anpassen kann!
Nicht schlecht! Aber das ist noch nicht einmal eine besonders erfinderische Art, die Darstellung von Paint-Worklets zu steuern. Wenn ich wollte, könnte ich einige zusätzliche Details hinzufügen, die nur in einem bestimmten Farbschema erscheinen, oder andere Dinge tun, um die Darstellung radikal zu ändern oder kleine Easter Eggs hinzuzufügen. Obwohl ich dieses Jahr viel gelernt habe, denke ich, dass diese Schnittstelle von APIs eine meiner Lieblingskombinationen war.