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. Sie baut auf den in Shadow DOM 101 und Shadow DOM 201 behandelten Konzepten auf.

Mehrere Schattenwurzeln verwenden

Wenn Sie eine Party organisieren, wird es nervig, wenn alle in den gleichen Raum drängen. Sie möchten Gruppen von Personen auf mehrere Breakouts verteilen. Auch Elemente, die Shadow DOM hosten, können das auch tun. Sie können also mehrere Shadow-Root-Elemente gleichzeitig hosten.

Sehen wir uns an, was passiert, wenn wir versuchen, mehrere Shadow-Roots an einen Host anzuhängen:

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

Was gerendert wird, ist „Root 2 FTW“, obwohl wir bereits einen Schattenbaum angehängt haben. Das liegt daran, dass der letzte Schattenbaum, der einem Host hinzugefügt wird, den Zuschlag erhält. So weit wie das Rendering ist ein LIFO-Stack. Dieses Verhalten kann anhand der Entwicklertools verifiziert werden.

Welchen Sinn hat es also, mehrere Schatten zu verwenden, wenn nur der letzte zur Rendering-Party eingeladen wird? Geben Sie Punkte für Schatteneinfügungen ein.

Schatten-Einfügepunkte

„Schatteneinfügungspunkte“ (<shadow>) ähneln normalen Einfügepunkten (<content>) insofern, als sie Platzhalter sind. Sie sind jedoch keine Platzhalter für die Inhalte eines Hosts, sondern die Hosts für andere Schattenbäume. Es ist Shadow DOM Inception.

Wie Sie sich wahrscheinlich vorstellen können, wird die Sache komplizierter, je weiter Sie das Kaninchenloch bohren. Aus diesem Grund ist in der Spezifikation sehr klar, was passiert, wenn mehrere <shadow>-Elemente verwendet werden:

In unserem ursprünglichen Beispiel wurde der erste Schatten root1 aus der Einladungsliste entfernt. Wenn Sie einen <shadow>-Platzhalter hinzufügen, wird der Code wiederhergestellt:

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

Bei diesem Beispiel gibt es einige interessante Aspekte:

  1. „Root 2 FTW“ wird weiterhin über „Root 1 FTW“ gerendert. Das liegt daran, dass der Einfügepunkt <shadow> eingefügt wurde. Wenn Sie umgekehrt möchten, verschieben Sie die Einfügemarke root2.innerHTML = '<shadow></shadow><div>Root 2 FTW</div>';.
  2. In „root1“ befindet sich jetzt der Einfügepunkt <content>. Dadurch kommt der Textknoten „Light DOM“ für das Rendering durch.

Was wird bei <shadow> gerendert?

Manchmal ist es hilfreich zu wissen, wie der ältere Schattenbaum bei <shadow> gerendert wird. Einen Verweis auf diesen Baum können Sie mit .olderShadowRoot abrufen:

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

Schattenstamm eines Hosts abrufen

Wenn ein Element Shadow-DOM hostet, können Sie mit .shadowRoot auf seinen jüngsten Schattenstamm 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 dringen, definieren Sie .shadowRoot neu:

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

Ein Hack, der funktioniert, aber er funktioniert. Letztlich sollten Sie bedenken, dass Shadow DOM keine Sicherheitsfunktion ist, obwohl es erstaunlich fantastisch ist. Verlassen Sie sich nicht darauf, um Inhalte vollständig zu isolieren.

Shadow-DOM in JS erstellen

Wenn Sie DOM-Elemente 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 vorherigen Abschnitt. Der einzige Unterschied besteht darin, dass ich jetzt select verwende, um die neu hinzugefügte <span> abzurufen.

Mit Einfügungspunkten arbeiten

Knoten, die aus dem Hostelement ausgewählt und im Schattenbaum "verteilt" werden, heißen ... Drumroll ... verteilte Knoten! Sie dürfen die Schattengrenze überschreiten, wenn sie von Einfügungspunkten eingeladen werden.

Das konkurrenzfähige an Einstiegspunkten ist jedoch, dass sie das DOM nicht verschieben. Die Knoten des Hosts bleiben intakt. Durch Einfügungen werden Knoten vom Host lediglich in den Schattenbaum neu projektiert. Es geht um Präsentations-/Rendering-Dinge: „Verschieben Sie diese Knoten hierher“, „Rendere diese Knoten an dieser Position.“

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à! Das h2 ist kein untergeordnetes Element des Schatten-DOM. Dies führt zu einem weiteren Tiden-Bit:

Element.getDistributedNodes()

<content> kann nicht aufgerufen werden, aber die .getDistributedNodes() API ermöglicht es uns, die verteilten Knoten an einem Einfügepunkt abzufragen:

<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, an welche Einfügepunkte ein Knoten verteilt ist, 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

Es ist schwierig, die schwarze Magie des Shadow DOM zu verstehen. Ich erinnere mich, dass ich mich zum ersten Mal darum gelegt habe.

Zur Visualisierung der Funktionsweise des Shadow-DOM-Renderings habe ich ein Tool mit d3.js erstellt. Beide Markup-Felder auf der linken Seite können bearbeitet werden. Fügen Sie Ihr eigenes Markup ein und sehen Sie sich an, wie die Abläufe funktionieren, und Einfügemarken verschieben Hostknoten in den Schattenbaum.

Shadow-DOM-Visualizer
Shadow DOM Visualizer starten

Probiere es aus und sag mir deine Meinung!

Ereignismodell

Einige Ereignisse überschreiten die Schattengrenze, andere wiederum nicht. In Fällen, in denen Ereignisse die Grenze überschreiten, wird das Ereignisziel angepasst, um die Kapselung beizubehalten, die die Obergrenze der Schattenwurzel bietet. Das Retargeting von Ereignissen sieht so aus, als stammten sie vom Hostelement und nicht aus internen Elementen aus dem Shadow DOM.

Aktion 1 spielen

  • Diese Frage ist interessant. Sie sollten ein mouseout vom Hostelement (<div data-host>) zum blauen Knoten sehen. Obwohl er ein verteilter Knoten ist, befindet er sich immer noch auf dem Host, nicht im ShadowDOM. Wenn der Mauszeiger wieder nach unten in Gelb bewegt wird, wird am blauen Knoten ein mouseout ausgelöst.

Aktion 2 spielen

  • Auf dem Host wird ein mouseout (ganz am Ende) angezeigt. Normalerweise würden 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 bewegt sich nicht durch die Obergrenze.

Aktion 3 spielen

  • Wenn Sie auf die Eingabe klicken, wird focusin nicht in der Eingabe, sondern auf dem Hostknoten selbst angezeigt. Retargeting!

Ereignisse, die immer gestoppt werden

Die folgenden Ereignisse überschreiten nie die Schattengrenze:

  • abort
  • error
  • auswählen
  • Ändern
  • Ladung
  • Zurücksetzen
  • resize
  • scroll
  • selectstart (Start auswählen)

Fazit

Ich hoffe, Sie werden dieser Aussage zustimmen: Shadow DOM ist eine unglaublich leistungsstarke Funktion. Zum ersten Mal bieten wir eine korrekte Datenkapselung ohne das zusätzliche Gepäck von <iframe>s oder anderen älteren Techniken.

Das Shadow DOM ist sicher ein komplexes Unterfangen, aber es lohnt sich, es der Webplattform hinzuzufügen. Nehmen Sie sich etwas Zeit damit. Erfahre es. Fragen stellen

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