CSS und Styling
In diesem Artikel werden weitere interessante Dinge beschrieben, die Sie mit Shadow DOM tun können. Es baut auf den Konzepten auf, die in Shadow DOM 101 erläutert werden. Eine Einführung finden Sie in diesem Artikel.
Einführung
Seien wir ehrlich. Unformatiertes Markup ist nicht gerade attraktiv. Glücklicherweise haben die klugen Köpfe hinter Web Components dies vorhergesehen und uns nicht im Stich gelassen. Das CSS Scoping Module definiert viele Optionen für das Styling von Inhalten in einem Shadow Tree.
Stilkapselung
Eine der Hauptfunktionen von Shadow DOM ist die Shadow-Grenze. Es hat viele gute Eigenschaften, aber eine der besten ist, dass es die Stil-Kapselung kostenlos bietet. Anders ausgedrückt:
<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>
Diese Demo bietet zwei interessante Erkenntnisse:
- Es gibt noch andere h3-Elemente auf dieser Seite, aber nur das im ShadowRoot entspricht dem h3-Selektor und wird daher rot formatiert. Auch hier werden standardmäßig bereichsbezogene Stile verwendet.
- Andere Stilregeln, die auf dieser Seite für h3-Elemente definiert sind, wirken sich nicht auf meine Inhalte aus. Das liegt daran, dass Selektoren die Schattengrenze nicht überschreiten.
Die Moral der Geschichte? Wir haben eine Stil-Kapselung von der Außenwelt. Danke, Shadow DOM!
Hostelement gestalten
Mit :host können Sie das Element auswählen und gestalten, das einen Shadow-Baum hostet:
<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>
Ein Problem ist, dass Regeln auf der übergeordneten Seite eine höhere Spezifität haben als :host-Regeln, die im Element definiert sind, aber eine niedrigere Spezifität als ein style-Attribut, das im Hostelement definiert ist. So können Nutzer Ihre Stile von außen überschreiben.
:host funktioniert auch nur im Kontext eines ShadowRoot, sodass Sie es nicht außerhalb des Shadow DOM verwenden können.
Mit der funktionalen Form von :host(<selector>) können Sie das Hostelement als Ziel festlegen, wenn es mit einem <selector> übereinstimmt.
Beispiel: Nur abgleichen, 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 Nutzerzustände (:hover, :focus, :active usw.) reagieren möchten.
<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 mit Designs versehen
Die Pseudoklasse :host-context(<selector>) stimmt mit dem Hostelement überein, wenn es oder eines seiner übergeordneten Elemente mit <selector> übereinstimmt.
:host-context() wird häufig verwendet, um ein Element basierend auf seiner Umgebung zu gestalten. Viele Nutzer wenden beispielsweise ein Design an, indem sie eine Klasse auf <html> oder <body> anwenden:
<body class="different">
<x-foo></x-foo>
</body>
Sie können :host-context(.different) verwenden, um <x-foo> zu formatieren, wenn es ein untergeordnetes Element eines Elements mit der Klasse .different ist:
:host-context(.different) {
color: red;
}
So können Sie Stilregeln im Shadow DOM eines Elements kapseln, die es basierend auf seinem Kontext eindeutig formatieren.
Unterstützung mehrerer Hosttypen innerhalb eines Shadow-Roots
Eine weitere Verwendung für :host ist, wenn Sie eine Designbibliothek erstellen und das Styling 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-interne Elemente von außen gestalten
Das Pseudoelement ::shadow und der Kombinator /deep/ sind wie ein Vorpal-Schwert der CSS-Autorität.
Sie ermöglichen es, die Grenze des Shadow DOM zu durchbrechen, um Elemente innerhalb von Shadow Trees zu gestalten.
Das ::shadow-Pseudoelement
Wenn ein Element mindestens einen Schattenbaum hat, entspricht das Pseudoelement ::shadow der Schattenwurzel selbst.
Damit können Sie Selektoren schreiben, die Knoten innerhalb des Shadow DOM eines Elements formatieren.
Wenn ein Element beispielsweise einen Shadow-Root hostet, können Sie #host::shadow span {} schreiben, um alle Spans innerhalb des Shadow-Baums zu formatieren.
<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 hat einen eigenen Shadow-Baum 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. Sie ignoriert alle Shadow-Grenzen und durchquert beliebig viele Shadow-Bäume. Einfach ausgedrückt: Mit /deep/ können Sie die Struktur eines Elements analysieren und auf jeden Knoten zugreifen.
Der Kombinator /deep/ ist besonders nützlich bei benutzerdefinierten Elementen, bei denen es üblich ist, mehrere Ebenen von Shadow DOM zu haben. Gute Beispiele dafür sind das Verschachteln einer Reihe von benutzerdefinierten Elementen (die jeweils einen eigenen Shadow Tree hosten) oder das Erstellen eines Elements, das von einem anderen Element mit <shadow> erbt.
Beispiel (benutzerdefinierte Elemente): Alle <x-panel>-Elemente auswählen, die Nachkommen von <x-tabs> sind, unabhängig von ihrer Position im Baum:
x-tabs /deep/ x-panel {
...
}
Beispiel: Alle Elemente mit der Klasse .library-theme an einer beliebigen Stelle in einem Shadow-Baum formatieren:
body /deep/ .library-theme {
...
}
Mit querySelector() arbeiten
Genauso wie .shadowRoot Shadow Trees für die DOM-Durchquerung öffnet, öffnen die Kombinatoren Shadow Trees für die Selector-Durchquerung.
Anstatt eine verschachtelte Kette von Wahnsinn 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 gestalten
Native HTML-Steuerelemente sind eine Herausforderung für das Styling. Viele geben einfach auf und entwickeln ihre eigene Lösung. Mit ::shadow und /deep/ kann jedoch jedes Element auf der Webplattform, das Shadow DOM verwendet, formatiert werden. Gute Beispiele sind die <input>-Typen und <video>:
video /deep/ input[type="range"] {
background: hotpink;
}
Stil-Hooks erstellen
Anpassung ist gut. In bestimmten Fällen möchten Sie möglicherweise Löcher in den Styling-Shield Ihres Shadow-DOMs bohren und Hooks für andere erstellen, um das Styling zu ermöglichen.
::shadow und /deep/ verwenden
/deep/ ist sehr leistungsstark. Komponentenautoren können damit einzelne Elemente als gestaltbar oder eine Reihe von Elementen als designfähig kennzeichnen.
Beispiel: Alle Elemente mit der Klasse .library-theme werden formatiert, wobei alle Shadow-Bäume ignoriert werden:
body /deep/ .library-theme {
...
}
Benutzerdefinierte Pseudoelemente verwenden
Sowohl WebKit als auch Firefox definieren Pseudoelemente für das Styling interner Teile nativer Browserelemente. Ein gutes Beispiel ist input[type=range]. Sie können den Schieberegler <span style="color:blue">blue</span> mit ::-webkit-slider-thumb gestalten:
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 außen gestaltbar kennzeichnen. Dies geschieht über benutzerdefinierte Pseudoelemente.
Mit dem Attribut pseudo können Sie ein Element als benutzerdefiniertes Pseudoelement festlegen.
Dem Wert oder Namen muss „x-“ vorangestellt werden. Dadurch wird eine Verknüpfung mit diesem Element im Shadow Tree erstellt und Außenstehenden wird ein bestimmter Pfad zum Überschreiten der Shadow-Grenze zugewiesen.
Hier ist ein Beispiel für das Erstellen eines benutzerdefinierten Schieberegler-Widgets, bei dem der Schieberegler blau formatiert werden kann:
<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 „Stilplatzhalter“, die andere Nutzer ausfüllen können.
Stellen Sie sich einen Autor eines benutzerdefinierten Elements vor, der Variablenplatzhalter in seinem Shadow DOM markiert. Eine für die Schriftart eines internen Buttons und eine für die Farbe:
button {
color: var(--button-text-color, pink); /* default color will be pink */
font-family: var(--button-font);
}
Anschließend definiert der Einbettende des Elements diese Werte nach seinen Wünschen. Vielleicht, um zum supercoolen 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 das wunderbar. Das Gesamtbild 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 werden zurückgesetzt
Vererbbare 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 die Property resetStyleInheritance, mit der Sie steuern können, was an der Shadow-Grenze passiert.
Betrachten Sie es als eine Möglichkeit, beim Erstellen einer neuen Komponente neu anzufangen.
resetStyleInheritance
false: Standardeinstellung. Vererbbare CSS-Eigenschaften werden weiterhin vererbt.true: Setzt vererbbare Attribute an der Grenze aufinitialzurück.
In der folgenden Demo wird gezeigt, wie sich die Änderung von resetStyleInheritance auf den Shadow Tree 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 schwieriger zu verstehen, vor allem, weil es sich nur auf vererbbare CSS-Eigenschaften auswirkt. Sie besagt: Wenn du nach einem Attribut suchst, das an der Grenze zwischen der Seite und dem ShadowRoot übernommen werden soll, übernimm keine Werte vom Host, sondern verwende stattdessen den Wert initial (gemäß der CSS-Spezifikation).
Wenn Sie sich nicht sicher sind, welche Eigenschaften in CSS vererbt werden, sehen Sie sich diese praktische Liste an oder aktivieren Sie im Steuerfeld „Element“ das Kästchen „Vererbte anzeigen“.
Verteilte Knoten formatieren
Verteilte Knoten sind Elemente, die an einem Einfügungspunkt (einem <content>-Element) gerendert werden. Mit dem <content>-Element kannst du Knoten aus dem Light DOM auswählen und an vordefinierten Stellen im Shadow DOM rendern. Sie befinden sich nicht logisch im Shadow DOM, sondern sind weiterhin untergeordnete Elemente des Hostelements. Einfügemarken sind nur ein Rendering-Element.
Verteilte Knoten behalten die Stile aus dem Hauptdokument bei. Das bedeutet, dass die Stilregeln der Hauptseite weiterhin für die Elemente gelten, auch wenn sie an einem Einfügepunkt gerendert werden. Auch hier gilt: Verteilte Knoten befinden sich logisch gesehen immer noch im Light DOM und werden nicht verschoben. Sie werden nur an anderer Stelle gerendert. Wenn die Knoten jedoch im Shadow DOM verteilt werden, können sie zusätzliche Stile annehmen, die im Shadow Tree definiert sind.
::content-Pseudoelement
Verteilte Knoten sind untergeordnete Elemente des Hostelements. Wie können wir sie also innerhalb des Shadow DOM ansprechen? Die Antwort ist das CSS-Pseudoelement ::content.
Damit können Light-DOM-Knoten angesprochen werden, die einen Einfügepunkt passieren. Beispiel:
::content > h3 formatiert alle h3-Tags, 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
Wenn Sie ein ShadowRoot-Element erstellen, können Sie die übernommenen Stile zurücksetzen.
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 Attribut reset-style-inheritance für das Element selbst.
Für einen ShadowRoot- oder
<shadow>-Einfügepunkt bedeutetreset-style-inheritance, dass vererbbare CSS-Eigenschaften auf dem Host aufinitialgesetzt werden, bevor sie auf den Shadow-Inhalt treffen. Diese Position wird als obere Grenze bezeichnet.Für
<content>-Einfügungspunkte:reset-style-inheritancebedeutet, dass vererbbare CSS-Eigenschaften aufinitialgesetzt werden, bevor die untergeordneten Elemente des Hosts am Einfügungspunkt verteilt werden. Diese Position wird als untere Grenze bezeichnet.
Fazit
Als Autoren 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 uns eine bereichsbezogene Stilkapselung und eine Möglichkeit, so viel (oder so wenig) von der Außenwelt zuzulassen, wie wir möchten. Durch die Definition benutzerdefinierter Pseudoelemente oder die Einbindung von CSS-Variablenplatzhaltern können Autoren Dritten praktische Styling-Hooks zur Verfügung stellen, um ihre Inhalte weiter anzupassen. Insgesamt haben Webautoren die volle Kontrolle darüber, wie ihre Inhalte dargestellt werden.