Shadow DOM 201

CSS und Stile

In diesem Artikel werden weitere praktische Funktionen von Shadow DOM beschrieben. Es basiert auf den im Shadow DOM 101 behandelten Konzepten. Eine Einführung finden Sie in diesem Artikel.

Einleitung

Seien wir ehrlich. Unformatiertes Markup ist nicht sexy. Zum Glück haben die brillanten Leute hinter Web Components dies vorausahnt und uns nicht hängen gelassen. Im CSS-Scoping-Modul werden viele Optionen für die Gestaltung von Inhalten in einem Schattenbaum definiert.

Stilkapselung

Eines der Kernmerkmale von Shadow DOM ist die Schattengrenze. Es hat viele schöne Eigenschaften, aber eine der besten ist, dass es die Stilkapselung kostenlos zur Verfügung stellt. 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>

Es gibt zwei interessante Beobachtungen bei dieser Demo:

  • Auf dieser Seite gibt es noch andere h3-Werte, aber das einzige, das mit dem h3-Selektor übereinstimmt und daher rot formatiert ist, ist das h3-Element im ShadowRoot. Auch hier sind standardmäßig auf einen Bereich reduzierte Stile festgelegt.
  • Andere auf dieser Seite definierte Stilregeln für h3s werden nicht in meinen Inhalt übernommen. Das liegt daran, dass Selektoren nicht die Schattengrenze überschreiten.

Die Moral der Geschichte? Wir bieten Stilkapselung von der Außenwelt. Danke, Shadow DOM!

Hostelement gestalten

Mit :host können Sie das Element, das einen Schattenbaum hostet, auswählen und gestalten:

<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 als die im Element definierten :host-Regeln haben, aber eine geringere Spezifität als ein style-Attribut, das im Hostelement definiert ist. So können Nutzer Ihren Stil von außen überschreiben. :host funktioniert auch nur in Verbindung mit ShadowRoot, d. h., es kann nicht außerhalb von Shadow DOM verwendet werden.

Mit der funktionalen Form von :host(<selector>) können Sie das Hostelement ausrichten, wenn es einem <selector> entspricht.

Beispiel: Damit wird nur abgeglichen, wenn das Element selbst die Klasse .different hat (z.B. <x-foo class="different"></x-foo>):

:host(.different) {
    ...
}

Auf Nutzerstatus reagieren

:host wird häufig verwendet, wenn Sie ein Custom Element erstellen und auf verschiedene Nutzerstatus reagieren möchten (:hover, :focus, :active usw.).

<style>
  :host {
    opacity: 0.4;
    transition: opacity 420ms ease-in-out;
  }
  :host(:hover) {
    opacity: 1;
  }
  :host(:active) {
    position: relative;
    top: 3px;
    left: 3px;
  }
</style>

Einem Element ein Design zuweisen

Die Pseudoklasse :host-context(<selector>) stimmt mit dem Hostelement überein, wenn es oder einer seiner Ancestors mit <selector> übereinstimmt.

:host-context() wird häufig verwendet, um ein Element basierend auf seiner Umgebung mit einem Thema zu versehen. Viele Nutzer führen beispielsweise Themen aus, 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 gestalten, wenn es ein Nachfolgerelement eines Elements mit der Klasse .different ist:

:host-context(.different) {
  color: red;
}

So haben Sie die Möglichkeit, Stilregeln im Shadow-DOM eines Elements zu kapseln, die es je nach Kontext individuell gestalten.

Unterstützung mehrerer Hosttypen von einem Schattenstamm aus

:host wird auch verwendet, wenn Sie eine Designbibliothek erstellen und das Gestalten vieler Arten von Hostelementen innerhalb desselben Shadow-DOMs 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>. */
}

Interne Elemente des Shadow-DOM von außen gestalten

Das Pseudoelement ::shadow und das Kombinationselement /deep/ sind wie ein Vorpal-Schwert für CSS-Autorität. Sie ermöglichen das Durchdringen der Grenzen des Shadow DOM, um Elemente innerhalb von Schattenbäumen zu gestalten.

Das ::shadow-Pseudoelement

Wenn ein Element mindestens einen Schattenbaum hat, stimmt das Pseudoelement ::shadow mit dem Schattenstamm selbst überein. Damit können Sie Selektoren schreiben, die Knoten innerhalb des Schattenbereichs eines Elements gestalten.

Wenn ein Element beispielsweise einen Schattenstamm hostet, können Sie #host::shadow span {} schreiben, um alle Spans innerhalb des Schattenbaums zu gestalten.

<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): Das Shadow-DOM von <x-tabs> hat <x-panel> untergeordnete Elemente. Jedes Feld hostet seinen eigenen Schattenbaum mit h2-Überschriften. Um diese Überschriften auf der Hauptseite zu gestalten, könnten Sie Folgendes schreiben:

x-tabs::shadow x-panel::shadow h2 {
    ...
}

Der /deep/-Combinator

Der Kombinator /deep/ ähnelt ::shadow, ist aber leistungsfähiger. Alle Schattengrenzen werden vollständig ignoriert und er geht in beliebig viele Schattenbäume über. Einfach ausgedrückt können Sie mit /deep/ die Eingeweide eines Elements untersuchen und ein Targeting auf jeden beliebigen Knoten vornehmen.

Die Kombination /deep/ ist besonders nützlich bei benutzerdefinierten Elementen, in denen häufig mehrere Ebenen von Shadow DOM verwendet werden. Erste Beispiele sind das Verschachteln einer Reihe von benutzerdefinierten Elementen, wobei jedes Element einen eigenen Schattenbaum hostet, oder mithilfe von <shadow> ein Element, das von einem anderen übernommen wird.

Beispiel (benutzerdefinierte Elemente): Wählen Sie an einer beliebigen Stelle in der Struktur alle <x-panel>-Elemente aus, die Nachfolger von <x-tabs> sind:

x-tabs /deep/ x-panel {
    ...
}

Beispiel: Gestalten Sie alle Elemente mit der Klasse .library-theme an einer beliebigen Stelle in einem Schattenbaum:

body /deep/ .library-theme {
    ...
}

Mit querySelector() arbeiten

Ähnlich wie .shadowRoot Schattenbäume für den DOM-Durchlauf öffnet, öffnen die Combinators Schattenbäume für den Selektordurchlauf. Anstatt eine verschachtelte Kette des Wahnsinns 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');

Stile für native Elemente festlegen

Die Gestaltung nativer HTML-Steuerelemente ist eine Herausforderung. Viele Leute geben einfach auf und rollen das. Mit ::shadow und /deep/ kann jedoch jedes Element auf der Webplattform, das Shadow DOM verwendet, gestaltet 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 vielleicht Löcher in das Stilschild des Schattens stechen und Haken erstellen, die andere gestalten können.

Mit ::shadow und /deep/

/deep/ hat eine Menge Energie. Damit können Komponentenautoren einzelne Elemente als Stilbar oder eine Reihe von Elementen als thematisch geeignet kennzeichnen.

Beispiel: Gestalten Sie alle Elemente der Klasse .library-theme und ignorieren Sie alle Schattenbäume:

body /deep/ .library-theme {
    ...
}

Benutzerdefinierte Pseudoelemente verwenden

Sowohl WebKit als auch Firefox definieren Pseudoelemente zum Gestalten interner Elemente nativer Browserelemente. Ein gutes Beispiel ist input[type=range]. Sie können den Schieberegler <span style="color:blue">blue</span> gestalten, indem Sie ::-webkit-slider-thumb ausrichten:

input[type=range].custom::-webkit-slider-thumb {
  -webkit-appearance: none;
  background-color: blue;
  width: 10px;
  height: 40px;
}

Ähnlich wie Browser, die Stil-Hooks in einige Internaten bereitstellen, können Autoren von Shadow DOM-Inhalten bestimmte Elemente als für Fremde nutzbar festlegen. Dies erfolgt mithilfe von benutzerdefinierten Pseudoelementen.

Mit dem Attribut pseudo können Sie ein Element als benutzerdefiniertes Pseudoelement festlegen. Sein Wert oder Name muss das Präfix „x-“ haben. Dadurch wird eine Verknüpfung mit diesem Element im Schattenbaum hergestellt und Außenstehenden wird eine bestimmte Spur geboten, über die sie die Schattengrenze überqueren können.

Hier sehen Sie ein Beispiel für das Erstellen eines benutzerdefinierten Schieberegler-Widgets, mit dem jemand seinen Schieberegler-Daumen blau gestalten 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 wirkungsvolle Methode zum Erstellen von Design-Hooks sind CSS-Variablen. Im Wesentlichen erstellen Sie „Stilplatzhalter“, die von anderen Nutzern ausgefüllt werden können.

Stellen Sie sich einen Autor eines benutzerdefinierten Elements vor, der Variablenplatzhalter in seinem Shadow DOM markiert. Eines für den Stil der Schriftart einer internen Schaltfläche und eine weitere für die Farbe:

button {
  color: var(--button-text-color, pink); /* default color will be pink */
  font-family: var(--button-font);
}

Dann definiert der Einbettunger des Elements diese Werte nach Bedarf. Vielleicht passend zum coolen Comic Sans-Thema der eigenen Seite:

#host {
  --button-text-color: green;
  --button-font: "Comic Sans MS", "Comic Sans", cursive;
}

Aufgrund der Art und Weise, wie CSS-Variablen übernommen werden, ist alles pfirsichfarben und das funktioniert wunderbar. Das ganze 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

Übernommene Stile wie Schriftarten, Farben und Linienhöhen wirken sich weiterhin auf Elemente im Shadow-DOM aus. Für maximale Flexibilität bietet uns das Schatten-DOM die Eigenschaft resetStyleInheritance, mit der wir steuern können, was an der Schattengrenze geschieht. Stellen Sie sich dies als eine Möglichkeit vor, bei der Erstellung einer neuen Komponente von vorn zu beginnen.

resetStyleInheritance

  • false ist die Standardeinstellung. Übernommene CSS-Eigenschaften werden weiterhin übernommen.
  • true: Setzt vererbbare Attribute an der Grenze auf initial zurück.

In der folgenden Demo sehen Sie, wie sich eine Ä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>
Von den Entwicklertools übernommene Eigenschaften

.resetStyleInheritance ist etwas schwieriger zu verstehen, da es sich nur auf CSS-Attribute auswirkt, die vererbbar sind. Diese besagt: Wenn Sie nach einer Property suchen, die übernommen werden soll, übernehmen Sie an der Grenze zwischen der Seite und dem ShadowRoot keine Werte vom Host, sondern verwenden Sie stattdessen den Wert initial (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 Steuerfeld „Element“ das Kästchen „Übernommene Elemente anzeigen“.

Verteilte Knoten gestalten

Verteilte Knoten sind Elemente, die an einem Einfügepunkt (einem <content>-Element) gerendert werden. Mit dem <content>-Element können Sie Knoten aus dem Light DOM auswählen und an vordefinierten Positionen in Ihrem Shadow DOM rendern. Sie befinden sich nicht logisch im Shadow-DOM; sie sind immer noch untergeordnete Elemente des Hostelements. Einfügungen sind reines Rendering.

Verteilte Knoten behalten Stile aus dem Hauptdokument bei. Das heißt, die Stilregeln von der Hauptseite gelten weiterhin für die Elemente, auch wenn sie an einer Einfügestelle gerendert werden. Verteilte Knoten befinden sich immer noch logisch in der leichten domäne und bewegen sich nicht. Sie rendern einfach an anderer Stelle. Wenn die Knoten jedoch im Shadow DOM verteilt werden, können sie zusätzliche Stile annehmen, die innerhalb des Schattenbaums definiert sind.

::content-Pseudoelement

Verteilte Knoten sind untergeordnete Objekte des Hostelements. Wie können wir sie innerhalb des Shadow-DOMs anvisieren? Die Antwort ist das CSS-Pseudoelement ::content. Damit lässt sich ein Targeting auf Light-DOM-Knoten vornehmen, die einen Einfügepunkt passieren. Beispiel:

::content > h3 gestaltet alle h3-Tags, die einen Einfügepunkt durchlaufen.

Hier ein Beispiel:

<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ügungspunkten zurücksetzen

Beim Erstellen eines ShadowRoot-Objekts kannst du die übernommenen Stile zurücksetzen. Für <content> und <shadow> Platzhalter gibt es diese Option ebenfalls. Wenn du diese Elemente verwendest, musst du entweder .resetStyleInheritance in JS festlegen oder das boolesche Attribut reset-style-inheritance für das Element selbst verwenden.

  • Bei einem ShadowRoot- oder <shadow>-Einfügungspunkt bedeutet reset-style-inheritance, dass vererbbare CSS-Eigenschaften auf dem Host auf initial gesetzt werden, bevor sie auf Ihren Schatteninhalt gelangen. Dieser Standort wird als Obergrenze bezeichnet.

  • Bei <content>-Einfügungspunkten bedeutet reset-style-inheritance, dass vererbbare CSS-Eigenschaften auf initial gesetzt werden, bevor die untergeordneten Elemente des Hosts am Einfügepunkt verteilt werden. Dieser Ort wird als Untergrenze bezeichnet.

Fazit

Als Autoren von benutzerdefinierten Elementen haben wir eine Vielzahl von Optionen, mit denen wir das Erscheinungsbild unserer Inhalte steuern können. Das Schatten-DOM bildet die Grundlage für diese neue Welt.

Das Shadow-DOM bietet eine bereichsspezifische Stilkapselung und die Möglichkeit, so viel (oder weniger) der Außenwelt zu berücksichtigen, wie wir möchten. Durch das Definieren benutzerdefinierter Pseudoelemente oder das Einbeziehen von Platzhaltern für CSS-Variablen können Autoren Dritten praktische Stil-Hooks zur Verfügung stellen, mit denen sie ihren Inhalt weiter anpassen können. Alles in allem haben Webautoren die volle Kontrolle darüber, wie ihre Inhalte dargestellt werden.