Shadow DOM 301

Erweiterte Konzepte und DOM APIs

In diesem Artikel erfahren Sie mehr über die tollen Möglichkeiten und Funktionen, die Shadow DOM bietet. Er baut auf den Konzepten auf, die in Shadow DOM 101 und Shadow DOM 201 besprochen wurden.

Mehrere Schattenwurzeln verwenden

Wenn Sie eine Party veranstalten, wird es schnell stickig, wenn sich alle in einem Raum drängen. Sie möchten Personengruppen auf mehrere Chatrooms verteilen. Das gilt auch für Elemente, die ein Shadow-DOM hosten, d. h., sie können mehrere Shadow-Roots gleichzeitig hosten.

Sehen wir uns an, was passiert, wenn wir versuchen, einem Host mehrere Schatten-Roots zuzuweisen:

<div id="example1">Light DOM</div>
<script>
  var container = document.querySelector('#example1');
  var root1 = container.createShadowRoot();
  var root2 = container.createShadowRoot();
  root1.innerHTML = '<div>Root 1 FTW</div>';
  root2.innerHTML = '<div>Root 2 FTW</div>';
</script>

Es wird „Wurzel 2 FTW“ gerendert, obwohl wir bereits einen Schattenbaum angehängt hatten. Das liegt daran, dass der letzte einem Host hinzugefügte Schattenbaum verwendet wird. Beim Rendern handelt es sich um einen LIFO-Stack. In den DevTools lässt sich dieses Verhalten bestätigen.

Was nützt es also, mehrere Schatten zu verwenden, wenn nur der letzte zum Rendering eingeladen wird? Geben Sie die Einfügungspunkte für den Schatten ein.

Schatteneinfügepunkte

„Schatteneinfügungspunkte“ (<shadow>) ähneln normalen Einfügungspunkten (<content>), da sie Platzhalter sind. Sie sind jedoch keine Platzhalter für den Inhalt eines Hosts, sondern Hosts für andere Schattenbäume. Hier ist Shadow DOM Inception!

Wie Sie sich vorstellen können, werden die Dinge komplizierter, je weiter Sie in die Materie eindringen. Aus diesem Grund wird in der Spezifikation sehr klar beschrieben, was passiert, wenn mehrere <shadow>-Elemente vorhanden sind:

Im ursprünglichen Beispiel stand der erste Schatten root1 nicht mehr auf der Einladungsliste zur Verfügung. Wenn Sie einen Einfügepunkt <shadow> hinzufügen, wird er wieder angezeigt:

<div id="example2">Light DOM</div>
<script>
var container = document.querySelector('#example2');
var root1 = container.createShadowRoot();
var root2 = container.createShadowRoot();
root1.innerHTML = '<div>Root 1 FTW</div><content></content>';
**root2.innerHTML = '<div>Root 2 FTW</div><shadow></shadow>';**
</script>

Dieses Beispiel hat einige interessante Aspekte:

  1. „Root 2 FTW“ wird weiterhin über „Root 1 FTW“ gerendert. Das liegt daran, wo wir den Einfügepunkt <shadow> platziert haben. Wenn Sie das Gegenteil wünschen, verschieben Sie den Einfügepunkt: root2.innerHTML = '<shadow></shadow><div>Root 2 FTW</div>';.
  2. Beachten Sie, dass in „root1“ jetzt ein Einfügepunkt <content> angezeigt wird. Dadurch wird der Textknoten „Light DOM“ für das Rendering verwendet.

Was wird bei <shadow> gerendert?

Manchmal ist es hilfreich zu wissen, welcher ältere Schattenbaum bei einer <shadow> gerendert wird. Sie können eine Referenz auf diesen Baum über .olderShadowRoot abrufen:

**root2.olderShadowRoot** === root1 //true

Schatten-Root eines Hosts abrufen

Wenn ein Element Shadow DOM hostet, können Sie über .shadowRoot auf das jüngste Shadow-Root zugreifen:

var root = host.createShadowRoot();
console.log(host.shadowRoot === root); // true
console.log(document.body.shadowRoot); // null

Wenn Sie befürchten, dass Personen in Ihre Schatten fallen, definieren Sie .shadowRoot als null:

Object.defineProperty(host, 'shadowRoot', {
  get: function() { return null; },
  set: function(value) { }
});

Ein bisschen ein Hack, aber es funktioniert. Denken Sie daran, dass Shadow DOM zwar eine fantastische Funktion ist, aber nicht als Sicherheitsfunktion konzipiert wurde. Sie können sich nicht darauf verlassen, dass die Inhalte vollständig voneinander getrennt werden.

Building Shadow DOM in JS

Wenn Sie das DOM lieber in JS erstellen möchten, bieten HTMLContentElement und HTMLShadowElement entsprechende Schnittstellen.

<div id="example3">
  <span>Light DOM</span>
</div>
<script>
var container = document.querySelector('#example3');
var root1 = container.createShadowRoot();
var root2 = container.createShadowRoot();

var div = document.createElement('div');
div.textContent = 'Root 1 FTW';
root1.appendChild(div);

 // HTMLContentElement
var content = document.createElement('content');
content.select = 'span'; // selects any spans the host node contains
root1.appendChild(content);

var div = document.createElement('div');
div.textContent = 'Root 2 FTW';
root2.appendChild(div);

// HTMLShadowElement
var shadow = document.createElement('shadow');
root2.appendChild(shadow);
</script>

Dieses Beispiel ist fast identisch mit dem im vorherigen Abschnitt. Der einzige Unterschied besteht darin, dass ich jetzt select verwende, um die neu hinzugefügte <span> abzurufen.

Mit Einfügepunkten arbeiten

Knoten, die aus dem Hostelement ausgewählt und in den Schattenbaum „verteilt“ werden, werden als…Trommelwirbel…verteilte Knoten bezeichnet. Sie dürfen die Schattengrenze überschreiten, wenn sie von Einfügepunkten eingeladen werden.

Das Konzept von Einfügepunkten ist etwas seltsam, da sie das DOM nicht physisch verschieben. Die Knoten des Hosts bleiben intakt. Bei Einfügungspunkten werden Knoten vom Host lediglich neu in den Schattenbaum projiziert. Es geht um die Präsentation/das Rendering: „Diese Knoten hierher verschieben“ „Diese Knoten an dieser Stelle rendern“

Beispiel:

<div><h2>Light DOM</h2></div>
<script>
var root = document.querySelector('div').createShadowRoot();
root.innerHTML = '<content select="h2"></content>';

var h2 = document.querySelector('h2');
console.log(root.querySelector('content[select="h2"] h2')); // null;
console.log(root.querySelector('content').contains(h2)); // false
</script>

Voilà! h2 ist kein untergeordnetes Element des Shadow-DOM. Das führt zu einem weiteren Tipp:

Element.getDistributedNodes()

Wir können nicht in eine <content> eindringen, aber mit der .getDistributedNodes() API können wir die verteilten Knoten an einem Einfügepunkt abfragen:

<div id="example4">
  <h2>Eric</h2>
  <h2>Bidelman</h2>
  <div>Digital Jedi</div>
  <h4>footer text</h4>
</div>

<template id="sdom">
  <header>
    <content select="h2"></content>
  </header>
  <section>
    <content select="div"></content>
  </section>
  <footer>
    <content select="h4:first-of-type"></content>
  </footer>
</template>

<script>
var container = document.querySelector('#example4');

var root = container.createShadowRoot();

var t = document.querySelector('#sdom');
var clone = document.importNode(t.content, true);
root.appendChild(clone);

var html = [];
[].forEach.call(root.querySelectorAll('content'), function(el) {
  html.push(el.outerHTML + ': ');
  var nodes = el.getDistributedNodes();
  [].forEach.call(nodes, function(node) {
    html.push(node.outerHTML);
  });
  html.push('\n');
});
</script>

Element.getDestinationInsertionPoints()

Ähnlich wie bei .getDistributedNodes() können Sie prüfen, in welche Einfügepunkte ein Knoten verteilt wird, indem Sie seinen .getDestinationInsertionPoints() aufrufen:

<div id="host">
  <h2>Light DOM
</div>

<script>
  var container = document.querySelector('div');

  var root1 = container.createShadowRoot();
  var root2 = container.createShadowRoot();
  root1.innerHTML = '<content select="h2"></content>';
  root2.innerHTML = '<shadow></shadow>';

  var h2 = document.querySelector('#host h2');
  var insertionPoints = h2.getDestinationInsertionPoints();
  [].forEach.call(insertionPoints, function(contentEl) {
    console.log(contentEl);
  });
</script>

Tool: Shadow DOM Visualizer

Das Verständnis der schwarzen Magie des Shadow DOM ist schwierig. Ich erinnere mich, dass ich zum ersten Mal versucht habe, das Ganze zu überwinden.

Um die Funktionsweise des Shadow-DOM-Renderings zu veranschaulichen, habe ich ein Tool mit d3.js erstellt. Beide Markup-Felder auf der linken Seite können bearbeitet werden. Fügen Sie einfach Ihr eigenes Markup ein und testen Sie, wie die Dinge funktionieren und wie sich Einfügungspunkte Hostknoten in den Schattenbaum bewegen.

Shadow DOM Visualizer
Shadow DOM-Visualizer starten

Probieren Sie es aus und lassen Sie uns wissen, was Sie davon halten.

Ereignismodell

Einige Ereignisse überschreiten die Schattengrenze, andere nicht. Wenn Ereignisse die Grenze überschreiten, wird das Ereignisziel angepasst, um die Kapselung beizubehalten, die durch die Obergrenze des Schatten-Stamms bereitgestellt wird. Das heißt, Ereignisse werden so umgeleitet, dass es so aussieht, als würden sie vom Hostelement stammen und nicht von internen Elementen im Shadow DOM.

Aktion 1 ausführen

  • Das ist interessant. Sie sollten eine mouseout vom Hostelement (<div data-host>) zum blauen Knoten sehen. Auch wenn es sich um einen verteilten Knoten handelt, befindet er sich immer noch im Host, nicht im ShadowDOM. Wenn Sie den Mauszeiger weiter nach unten in den gelben Bereich bewegen, wird wieder ein mouseout auf dem blauen Knoten angezeigt.

Aktion 2 abspielen

  • Es gibt ein mouseout, das auf dem Host ganz am Ende angezeigt wird. Normalerweise werden mouseout-Ereignisse für alle gelben Blöcke ausgelöst. In diesem Fall befinden sich diese Elemente jedoch innerhalb des Shadow DOM und das Ereignis verläuft nicht durch seine obere Begrenzung.

Play Action 3

  • Wenn Sie auf die Eingabe klicken, wird das focusin nicht in der Eingabe, sondern auf dem Hostknoten selbst angezeigt. Es wurde eine neue Ausrichtung vorgenommen.

Ereignisse, die immer beendet werden

Die folgenden Ereignisse überschreiten niemals die Schattengrenze:

  • abort
  • Fehler
  • auswählen
  • Ändern
  • load
  • Zurücksetzen
  • resize
  • scroll
  • selectstart

Fazit

Ich hoffe, Sie stimmen mir zu, dass Shadow DOM unglaublich leistungsfähig ist. Zum ersten Mal haben wir eine ordnungsgemäße Kapselung ohne das zusätzliche Gewicht von <iframe>s oder anderen älteren Techniken.

Shadow DOM ist zwar ein komplexes Thema, aber es lohnt sich, es in die Webplattform aufzunehmen. Nehmen Sie sich Zeit. Lernen Sie es. Fragen stellen

Weitere Informationen finden Sie im Einführungsartikel Shadow DOM 101 von Dominic und in Shadow DOM 201: CSS & Styling.