Shadow DOM 201

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-Gültigkeitsmodul definiert viele Optionen für das Gestalten von Inhalten in einer Schattenstruktur.

Stilkapselung

Eine der Hauptfunktionen von Shadow DOM ist die Schattengrenze. Es hat viele tolle Eigenschaften, aber das Beste ist, dass die Stilkapselung kostenlos ist. 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:

  • Es gibt weitere h3s auf dieser Seite, aber das einzige h3-Element, das mit dem h3-Selektor übereinstimmt und daher rot dargestellt wird, ist das h3-Element in ShadowRoot. Standardmäßig auf einen Bereich reduzierte Stile.
  • 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 :host können Sie das Element auswählen und gestalten, das eine Schattenstruktur 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>

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

:host wird häufig verwendet, wenn Sie ein benutzerdefiniertes Element erstellen und auf verschiedene Nutzerstatus (z. B. Mausbewegung, Fokus, :aktiv) 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>

Design für Elemente festlegen

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;
}

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

Unterstützung mehrerer Hosttypen innerhalb eines Schattenstamms

Sie können :host auch verwenden, wenn Sie eine Themenbibliothek erstellen und viele Arten von Hostelementen innerhalb desselben Shadow DOM gestalten 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 das Durchbohren der Begrenzung des Shadow DOM, um Elemente innerhalb von Schattenbäumen zu gestalten.

Das Pseudoelement ::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 Schattenstamm hostet, können Sie #host::shadow span {} eingeben, um alle SPANs innerhalb des Schattenbaums 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 /deep/-Kombinator ähnelt ::shadow, ist aber leistungsfähiger. Dabei werden alle Schattengrenzen ignoriert und beliebig viele Schattenbäume durchquert. Einfach ausgedrückt können Sie mit /deep/ die Eingeweide eines Elements aufschlüsseln und einen beliebigen Knoten auswählen.

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 erbt.

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: Gestalten Sie alle Elemente der Klasse .library-theme an beliebiger Stelle in einer Schattenstruktur:

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 ihren eigenen. 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.

Mit ::shadow und /deep/

Die Leistung von /deep/ ist sehr stark. 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>“ durch Targeting auf ::-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 externen Elementen stilisierbar kennzeichnen. Dazu werden benutzerdefinierte Pseudoelemente verwendet.

Mit dem Attribut pseudo können Sie ein Element als benutzerdefiniertes Pseudoelement 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, ist alles pfirsichlich und das funktioniert gut. 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. Für übernehmbare CSS-Eigenschaften wird weiterhin die Einstellung übernommen.
  • true – Setzt vererbbare Eigenschaften an der Grenze auf initial zurück.

Unten sehen Sie eine Demo, die zeigt, 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>
Übernommene Properties in DevTools

.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“.

Verteilte Knoten gestalten

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 heißt, die Stilregeln der Hauptseite gelten weiterhin für die Elemente, auch wenn sie an einer Einfügestelle 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 untergeordnete Knoten des Hostelements. Wie können wir sie also innerhalb des Shadow DOM ansprechen? Die Antwort ist das CSS-Pseudoelement ::content. So können Sie Light-DOM-Knoten anvisieren, die einen Einfügepunkt passieren. Beispiel:

::content > h3 fügt allen h3-Tags, die einen Einfügepunkt passieren, einen Stil hinzu.

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 von ShadowRoot können Sie die übernommenen Stile zurücksetzen. Diese Option gibt es auch für <content>- und <shadow>-Einfügungspunkte. 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.

  • Bei ShadowRoot- oder <shadow>-Einfügungspunkten bedeutet reset-style-inheritance, dass vererbbare CSS-Eigenschaften auf dem Host auf initial gesetzt werden, bevor sie auf den Schatteninhalt treffen. Dieser Ort wird als Obergrenze bezeichnet.

  • Für <content>-Einfügepunkte: reset-style-inheritance bedeutet, dass vererbbare CSS-Properties auf initial 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.