CSS und Stil
In diesem Artikel werden weitere tolle Dinge beschrieben, die Sie mit Shadow DOM tun können. Er basiert auf den Konzepten, die in Shadow DOM 101 erläutert wurden. Eine Einführung finden Sie in diesem Artikel.
Einführung
Seien wir ehrlich. Unformatiertes Markup ist nicht besonders sexy. Glücklicherweise haben die genialen Köpfe hinter Web Components dies vorausgesehen und uns nicht im Stich gelassen. Das CSS-Skalierungsmodul definiert viele Optionen zum Stilisieren von Inhalten in einem Schattenbaum.
Stilkapselung
Eine der Hauptfunktionen von Shadow DOM ist die Schattengrenze. Es hat viele schöne Eigenschaften, aber eine der besten ist, dass es eine kostenlose Stilkapselung bietet. Mit anderen Worten:
<div><h3>Light DOM</h3></div>
<script>
var root = document.querySelector('div').createShadowRoot();
root.innerHTML = `
<style>
h3 {
color: red;
}
</style>
<h3>Shadow DOM</h3>
`;
</script>
Bei dieser Demo gibt es zwei interessante Beobachtungen:
- Auf dieser Seite gibt es noch weitere H3-Elemente, aber nur dasjenige im ShadowRoot stimmt mit dem H3-Selektor überein und ist daher rot formatiert. Auch hier werden standardmäßig Stile mit Bereich verwendet.
- Andere Stilregeln, die auf dieser Seite für H3-Elemente definiert sind, werden nicht auf meine Inhalte angewendet. Das liegt daran, dass Auswahlen die Schattengrenze nicht überschreiten.
Was ist die Moral der Geschichte? Wir haben eine Stilkapselung von der Außenwelt. Danke Shadow DOM!
Hostelement stylen
Mit der :host
können Sie das Element auswählen und stilisieren, das einen Schattenbaum enthält:
<button class="red">My Button</button>
<script>
var button = document.querySelector('button');
var root = button.createShadowRoot();
root.innerHTML = `
<style>
:host {
text-transform: uppercase;
}
</style>
<content></content>
`;
</script>
Eine Besonderheit ist, dass Regeln auf der übergeordneten Seite spezifischer sind als :host
-Regeln, die im Element definiert sind, aber weniger spezifisch als ein style
-Attribut, das im Hostelement definiert ist. So können Nutzer Ihr Styling von außen überschreiben.
:host
funktioniert auch nur im Kontext eines ShadowRoots und kann nicht außerhalb des Shadow-DOM verwendet werden.
Mit der funktionalen Form von :host(<selector>)
können Sie das Hostelement anvisieren, wenn es mit einem <selector>
übereinstimmt.
Beispiel: Übereinstimmung nur, wenn das Element selbst die Klasse .different
hat (z.B. <x-foo class="different"></x-foo>
):
:host(.different) {
...
}
Auf Nutzerstatus reagieren
Ein häufiger Anwendungsfall für :host
ist, wenn Sie ein benutzerdefiniertes Element erstellen und auf verschiedene Nutzerstatus reagieren möchten (z. B. :hover, :focus, :active).
<style>
:host {
opacity: 0.4;
transition: opacity 420ms ease-in-out;
}
:host(:hover) {
opacity: 1;
}
:host(:active) {
position: relative;
top: 3px;
left: 3px;
}
</style>
Elemente einfärben
Die Pseudoklasse :host-context(<selector>)
stimmt mit dem Hostelement überein, wenn es oder einer seiner Vorfahren mit <selector>
übereinstimmt.
:host-context()
wird häufig verwendet, um ein Element anhand seiner Umgebung zu thematisieren. Viele Nutzer wenden beispielsweise einen Kurs auf <html>
oder <body>
an, um ein bestimmtes Design zu verwenden:
<body class="different">
<x-foo></x-foo>
</body>
Sie können :host-context(.different)
verwenden, um <x-foo>
zu formatieren, wenn es ein Abkömmling eines Elements mit der Klasse .different
ist:
:host-context(.different) {
color: red;
}
So können Sie Stilregeln im Shadow DOM eines Elements einkapseln, die es je nach Kontext individuell stylen.
Unterstützung mehrerer Hosttypen innerhalb eines Schattenstammverzeichnisses
Eine weitere Verwendungsmöglichkeit für :host
ist, wenn Sie eine Bibliothek für Designs erstellen und das Stylen vieler Arten von Hostelementen innerhalb desselben Shadow DOM unterstützen möchten.
:host(x-foo) {
/* Applies if the host is a <x-foo> element.*/
}
:host(x-foo:host) {
/* Same as above. Applies if the host is a <x-foo> element. */
}
:host(div) {
/* Applies if the host element is a <div>. */
}
Shadow-DOM-Inhalte von außen stylen
Das ::shadow
-Pseudoelement und der /deep/
-Kombinator sind wie ein Vorpal-Schwert der CSS-Autorität.
Sie ermöglichen es, die Grenze des Shadow DOM zu durchdringen, um Elemente innerhalb von Shadow-Trees zu stylen.
Das Pseudo-Element ::shadow
Wenn ein Element mindestens einen Schattenbaum hat, entspricht das ::shadow
-Pseudoelement dem Schattenknoten selbst.
Damit können Sie Selektoren schreiben, die Knoten im internen Shadow-DOM eines Elements stylen.
Wenn ein Element beispielsweise einen Schattenknoten hostet, können Sie #host::shadow span {}
eingeben, um alle SPANs in seinem Schattenbaum zu stylen.
<style>
#host::shadow span {
color: red;
}
</style>
<div id="host">
<span>Light DOM</span>
</div>
<script>
var host = document.querySelector('div');
var root = host.createShadowRoot();
root.innerHTML = `
<span>Shadow DOM</span>
<content></content>
`;
</script>
Beispiel (benutzerdefinierte Elemente): <x-tabs>
hat <x-panel>
untergeordnete Elemente im Shadow DOM. Jeder Bereich enthält einen eigenen Schattenbaum mit h2
-Überschriften. Um diese Überschriften auf der Hauptseite zu formatieren, könnte man Folgendes schreiben:
x-tabs::shadow x-panel::shadow h2 {
...
}
Der /deep/-Kombinator
Der Kombinator /deep/
ähnelt ::shadow
, ist aber leistungsfähiger. Dabei werden alle Schattengrenzen ignoriert und beliebig viele Schattenbäume durchquert. Mit /deep/
können Sie sich also in die Struktur eines Elements eingraben und auf einen beliebigen Knoten abzielen.
Der /deep/
-Kombinator ist besonders nützlich bei benutzerdefinierten Elementen, bei denen es häufig mehrere Ebenen des Shadow DOM gibt. Gute Beispiele sind das Verschachteln mehrerer benutzerdefinierter Elemente (die jeweils einen eigenen Schattenbaum haben) oder das Erstellen eines Elements, das mit <shadow>
von einem anderen Element übernommen wird.
Beispiel (benutzerdefinierte Elemente): Alle <x-panel>
-Elemente auswählen, die von <x-tabs>
abgeleitet sind, und zwar überall im Baum:
x-tabs /deep/ x-panel {
...
}
Beispiel: Alle Elemente mit der Klasse .library-theme
in einem Schattenbaum stylen:
body /deep/ .library-theme {
...
}
Mit querySelector() arbeiten
Genau wie .shadowRoot
Schattenbäume für die DOM-Durchquerung öffnet, öffnen die Kombinatoren Schattenbäume für die Auswahldurchquerung.
Anstatt eine verschachtelte Kette von Anweisungen zu schreiben, können Sie eine einzelne Anweisung schreiben:
// No fun.
document.querySelector('x-tabs').shadowRoot
.querySelector('x-panel').shadowRoot
.querySelector('#foo');
// Fun.
document.querySelector('x-tabs::shadow x-panel::shadow #foo');
Native Elemente stylen
Native HTML-Steuerelemente sind eine Herausforderung für den Stil. Viele geben einfach auf und rollen ihre eigenen Zigaretten. Mit ::shadow
und /deep/
kann jedoch jedes Element auf der Webanwendung, das Shadow DOM verwendet, gestaltet werden. Gute Beispiele sind die Typen <input>
und <video>
:
video /deep/ input[type="range"] {
background: hotpink;
}
Stil-Hooks erstellen
Die Anpassung ist gut. In bestimmten Fällen kann es sinnvoll sein, Löcher in den Styling-Schirm Ihres Schattens zu bohren und Hooks zu erstellen, die andere stylen können.
::shadow und /deep/ verwenden
/deep/
bietet viele Möglichkeiten. So können Komponentenautoren einzelne Elemente als stilisierbar oder eine Reihe von Elementen als thematisierbar kennzeichnen.
Beispiel: Alle Elemente mit der Klasse .library-theme
werden formatiert, alle Schattenbäume werden ignoriert:
body /deep/ .library-theme {
...
}
Benutzerdefinierte Pseudoelemente verwenden
Sowohl WebKit als auch Firefox definieren Pseudoelemente zum Stylen interner Teile nativer Browserelemente. Ein gutes Beispiel ist die input[type=range]
. Sie können den Schieberegler <span style="color:blue">blue</span>
mithilfe von ::-webkit-slider-thumb
stylen:
input[type=range].custom::-webkit-slider-thumb {
-webkit-appearance: none;
background-color: blue;
width: 10px;
height: 40px;
}
Ähnlich wie Browser Styling-Hooks für einige interne Elemente bereitstellen, können Autoren von Shadow-DOM-Inhalten bestimmte Elemente als von externen Elementen stilisierbar kennzeichnen. Dazu werden benutzerdefinierte Pseudoelemente verwendet.
Mit dem Attribut pseudo
können Sie ein Element als benutzerdefiniertes Pseudo-Element festlegen.
Der Wert oder Name muss mit „x-“ beginnen. Dadurch wird eine Verknüpfung mit diesem Element im Schattenbaum hergestellt und Außenstehenden wird eine bestimmte Spur zum Überqueren der Schattengrenze zugewiesen.
Hier ist ein Beispiel für ein benutzerdefiniertes Schieberegler-Widget, bei dem Nutzer den Schieberegler blau gestalten können:
<style>
#host::x-slider-thumb {
background-color: blue;
}
</style>
<div id="host"></div>
<script>
var root = document.querySelector('#host').createShadowRoot();
root.innerHTML = `
<div>
<div pseudo="x-slider-thumb"></div>' +
</div>
`;
</script>
CSS-Variablen verwenden
Eine leistungsstarke Möglichkeit, Design-Hooks zu erstellen, sind CSS-Variablen. Im Grunde erstellen Sie „Stil-Platzhalter“, die andere Nutzer ausfüllen können.
Angenommen, ein Entwickler von benutzerdefinierten Elementen markiert Variablen-Platzhalter in seinem Shadow DOM. Eines für die Schriftart und eines für die Farbe einer internen Schaltfläche:
button {
color: var(--button-text-color, pink); /* default color will be pink */
font-family: var(--button-font);
}
Der Einbettungscode des Elements definiert dann diese Werte nach Belieben. Vielleicht, um zum super coolen Comic Sans-Design der eigenen Seite zu passen:
#host {
--button-text-color: green;
--button-font: "Comic Sans MS", "Comic Sans", cursive;
}
Aufgrund der Art und Weise, wie CSS-Variablen übernommen werden, funktioniert alles einwandfrei. Das vollständige Bild sieht so aus:
<style>
#host {
--button-text-color: green;
--button-font: "Comic Sans MS", "Comic Sans", cursive;
}
</style>
<div id="host">Host node</div>
<script>
var root = document.querySelector('#host').createShadowRoot();
root.innerHTML = `
<style>
button {
color: var(--button-text-color, pink);
font-family: var(--button-font);
}
</style>
<content></content>
`;
</script>
Stile zurücksetzen
Übernehmbare Stile wie Schriftarten, Farben und Zeilenhöhen wirken sich weiterhin auf Elemente im Shadow DOM aus. Für maximale Flexibilität bietet Shadow DOM jedoch das Attribut resetStyleInheritance
, mit dem wir steuern können, was an der Schattengrenze passiert.
Sie können damit beim Erstellen einer neuen Komponente einen Neuanfang machen.
resetStyleInheritance
false
– Standardeinstellung. Vererbbare CSS-Properties werden weiterhin übernommen.true
– Setzt vererbbare Eigenschaften an der Grenze aufinitial
zurück.
Unten sehen Sie eine Demo, in der gezeigt wird, wie sich die Änderung von resetStyleInheritance
auf den Schattenbaum auswirkt:
<div>
<h3>Light DOM</h3>
</div>
<script>
var root = document.querySelector('div').createShadowRoot();
root.resetStyleInheritance = <span id="code-resetStyleInheritance">false</span>;
root.innerHTML = `
<style>
h3 {
color: red;
}
</style>
<h3>Shadow DOM</h3>
<content select="h3"></content>
`;
</script>
<div class="demoarea" style="width:225px;">
<div id="style-ex-inheritance"><h3 class="border">Light DOM</div>
</div>
<div id="inherit-buttons">
<button id="demo-resetStyleInheritance">resetStyleInheritance=false</button>
</div>
<script>
var container = document.querySelector('#style-ex-inheritance');
var root = container.createShadowRoot();
//root.resetStyleInheritance = false;
root.innerHTML = '<style>h3{ color: red; }</style><h3>Shadow DOM<content select="h3"></content>';
document.querySelector('#demo-resetStyleInheritance').addEventListener('click', function(e) {
root.resetStyleInheritance = !root.resetStyleInheritance;
e.target.textContent = 'resetStyleInheritance=' + root.resetStyleInheritance;
document.querySelector('#code-resetStyleInheritance').textContent = root.resetStyleInheritance;
});
</script>
.resetStyleInheritance
ist etwas komplizierter, vor allem weil es sich nur auf CSS-Eigenschaften auswirkt, die vererbbar sind. Dort steht: Wenn Sie nach einer zu übergeordneten Eigenschaft suchen, sollten Sie an der Grenze zwischen der Seite und dem ShadowRoot keine Werte vom Host übernehmen, sondern stattdessen den Wert initial
verwenden (gemäß der CSS-Spezifikation).
Wenn Sie nicht sicher sind, welche Eigenschaften in CSS übernommen werden, sehen Sie sich diese praktische Liste an oder aktivieren Sie im Bereich „Element“ das Kästchen „Übernommen anzeigen“.
Stil für verteilte Knoten festlegen
Verteilte Knoten sind Elemente, die an einem Einfügepunkt (einem <content>
-Element) gerendert werden. Mit dem Element <content>
können Sie Knoten aus dem Light DOM auswählen und an vordefinierten Stellen im Shadow DOM rendern. Sie befinden sich logisch nicht im Shadow-DOM, sondern sind weiterhin untergeordnete Elemente des Hostelements. Einfügepunkte sind nur für das Rendern relevant.
Auf verteilten Knoten werden die Stile aus dem Hauptdokument beibehalten. Das bedeutet, dass Stilregeln von der Hauptseite weiterhin auf die Elemente angewendet werden, auch wenn sie an einem Einfügepunkt gerendert werden. Wie bereits erwähnt, befinden sich verteilte Knoten logischerweise immer noch im Light-DOM und bewegen sich nicht. Sie werden nur an anderer Stelle gerendert. Wenn die Knoten jedoch in das Shadow DOM verteilt werden, können sie zusätzliche Stile annehmen, die im Shadow-Baum definiert sind.
Pseudo-Element ::content
Verteilte Knoten sind untergeordnet dem Hostelement. Wie können wir sie also innerhalb des Shadow-DOM ansteuern? Die Antwort ist das CSS-Pseudoelement ::content
.
So können Sie Light-DOM-Knoten anvisieren, die einen bestimmten Einfügepunkt passieren. Beispiel:
::content > h3
fügt allen h3
-Tags einen Stil hinzu, die einen Einfügepunkt passieren.
Sehen wir uns ein Beispiel an:
<div>
<h3>Light DOM</h3>
<section>
<div>I'm not underlined</div>
<p>I'm underlined in Shadow DOM!</p>
</section>
</div>
<script>
var div = document.querySelector('div');
var root = div.createShadowRoot();
root.innerHTML = `
<style>
h3 { color: red; }
content[select="h3"]::content > h3 {
color: green;
}
::content section p {
text-decoration: underline;
}
</style>
<h3>Shadow DOM</h3>
<content select="h3"></content>
<content select="section"></content>
`;
</script>
Stile an Einfügepunkten zurücksetzen
Beim Erstellen eines ShadowRoots haben Sie die Möglichkeit, die übernommenen Stile zurückzusetzen.
Diese Option ist auch für die Einfügepunkte <content>
und <shadow>
verfügbar. Wenn Sie diese Elemente verwenden, legen Sie entweder .resetStyleInheritance
in JS fest oder verwenden Sie das boolesche reset-style-inheritance
-Attribut für das Element selbst.
Für ShadowRoot- oder
<shadow>
-Einfügepunkte:reset-style-inheritance
bedeutet, dass vererbbare CSS-Properties beim Host aufinitial
gesetzt werden, bevor sie auf Ihre Schatteninhalte treffen. Dieser Ort wird als Obergrenze bezeichnet.Für
<content>
-Einfügepunkte:reset-style-inheritance
bedeutet, dass vererbbare CSS-Properties aufinitial
festgelegt werden, bevor die untergeordneten Elemente des Hosts am Einfügepunkt verteilt werden. Dieser Ort wird als untere Grenze bezeichnet.
Fazit
Als Ersteller von benutzerdefinierten Elementen haben wir viele Möglichkeiten, das Erscheinungsbild unserer Inhalte zu steuern. Shadow DOM bildet die Grundlage für diese neue Welt.
Shadow DOM bietet eine stilbezogene Kapselung und die Möglichkeit, so viel oder so wenig von der Außenwelt zuzulassen, wie wir möchten. Durch das Definieren benutzerdefinierter Pseudoelemente oder das Einfügen von Platzhaltern für CSS-Variablen können Autoren Drittanbietern praktische Styling-Hooks zur Verfügung stellen, um ihre Inhalte weiter anzupassen. Webautoren haben also die volle Kontrolle darüber, wie ihre Inhalte dargestellt werden.