Erweiterte Konzepte und DOM APIs
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 und Shadow DOM 201 erläutert werden.
Mehrere Shadow-Roots verwenden
Wenn Sie eine Party veranstalten, wird es stickig, wenn alle im selben Raum sind. Sie möchten die Möglichkeit haben, Gruppen von Personen auf mehrere Räume zu verteilen. Elemente, die Shadow DOM hosten, können dies ebenfalls tun, d. h. sie können mehrere Shadow Roots 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>
Es wird „Root 2 FTW“ gerendert, obwohl wir bereits einen Shadow Tree angehängt hatten. Das liegt daran, dass der letzte Shadow Tree, der einem Host hinzugefügt wurde, gewinnt. In Bezug auf das Rendering ist es ein LIFO-Stack. In den Entwicklertools können Sie dieses Verhalten überprüfen.
Was bringt es also, mehrere Schatten zu verwenden, wenn nur der letzte gerendert wird? Geben Sie Schatten-Einfügepunkte ein.
Schatten-Einfügepunkte
„Shadow-Einfügemarken“ (<shadow>) ähneln normalen Einfügemarken (<content>) darin, dass sie Platzhalter sind. Anstatt Platzhalter für den Inhalt eines Hosts zu sein, sind sie jedoch Hosts für andere Shadow-Bäume.
Das ist Shadow DOM Inception!
Wie Sie sich wahrscheinlich vorstellen können, wird es immer komplizierter, je tiefer Sie in die Materie einsteigen. Aus diesem Grund wird in der Spezifikation sehr genau beschrieben, was passiert, wenn mehrere <shadow>-Elemente im Spiel sind:
In unserem ursprünglichen Beispiel wurde der erste Schatten root1 nicht in die Einladungsliste aufgenommen. Wenn Sie einen <shadow>-Einfügungspunkt 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 ist aus mehreren Gründen interessant:
- „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 möchten, verschieben Sie die Einfügemarke:root2.innerHTML = '<shadow></shadow><div>Root 2 FTW</div>';. - In root1 gibt es jetzt einen
<content>-Einfügepunkt. Dadurch wird der Textknoten „Light DOM“ beim Rendern berücksichtigt.
Was wird unter <shadow> gerendert?
Manchmal ist es nützlich, den älteren Shadow Tree zu kennen, der bei einem <shadow> gerendert wird. Sie können über .olderShadowRoot einen Verweis auf diesen Baum abrufen:
**root2.olderShadowRoot** === root1 //true
Shadow-Root eines Hosts abrufen
Wenn ein Element Shadow-DOM hostet, können Sie mit .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 Nutzer in Ihre Schatten eindringen, definieren Sie .shadowRoot als null:
Object.defineProperty(host, 'shadowRoot', {
get: function() { return null; },
set: function(value) { }
});
Das ist zwar ein kleiner Trick, aber er funktioniert. Letztendlich ist es wichtig, daran zu denken, dass das Shadow DOM zwar fantastisch ist, aber nicht als Sicherheitsfunktion konzipiert wurde. Verlassen Sie sich nicht darauf, dass Inhalte vollständig isoliert werden.
Shadow-DOM in JS erstellen
Wenn Sie das DOM lieber in JS erstellen, gibt es dafür Schnittstellen in HTMLContentElement und HTMLShadowElement.
<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 Shadow-Baum „verteilt“ werden, werden als verteilte Knoten bezeichnet. Sie dürfen die Schattengrenze überschreiten, wenn sie von Einfügungspunkten eingeladen werden.
Das Konzept von Einfügemarken ist insofern ungewöhnlich, als sie das DOM nicht physisch verschieben. Die Knoten des Hosts bleiben intakt. Einfügungspunkte projizieren lediglich Knoten vom Host in den Shadow-Baum. Es ist eine Präsentations-/Rendering-Sache: „Verschiebe 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 Shadow DOM. Das führt zu einem weiteren Tipp:
Element.getDistributedNodes()
Wir können nicht in ein <content>-Element vordringen, 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ügungspunkte ein Knoten verteilt wird, indem Sie seine .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
Die Funktionsweise des Shadow DOM ist nicht so einfach zu verstehen. Ich erinnere mich noch, wie ich mich zum ersten Mal damit beschäftigt habe.
Um zu veranschaulichen, wie das Shadow DOM-Rendering funktioniert, habe ich ein Tool mit d3.js erstellt. Beide Markup-Felder auf der linken Seite können bearbeitet werden. Sie können Ihr eigenes Markup einfügen und damit herumspielen, um zu sehen, wie die Dinge funktionieren und wie Einfügemarken Hostknoten in den Shadow-Baum einfügen.
Probieren Sie es aus und lassen Sie mich 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 die obere Grenze des Shadow-Roots bietet. Das bedeutet, dass Ereignisse so umgeleitet werden, dass sie so aussehen, als kämen sie vom Hostelement und nicht von internen Elementen des Shadow DOM.
Play Action 1
- Das ist interessant. Sie sollten einen
mouseoutvom Hostelement (<div data-host>) zum blauen Knoten sehen. Obwohl es sich um einen verteilten Knoten handelt, befindet er sich immer noch im Host und nicht im ShadowDOM. Wenn Sie die Maus weiter nach unten in den gelben Bereich bewegen, wird wieder einmouseoutauf dem blauen Knoten ausgelöst.
Play Action 2
- Es gibt ein
mouseout, das auf dem Host (ganz am Ende) angezeigt wird. Normalerweise werdenmouseout-Ereignisse für alle gelben Blöcke ausgelöst. In diesem Fall befinden sich diese Elemente jedoch im Shadow DOM und das Ereignis wird nicht über die obere Grenze hinaus weitergegeben.
Play Action 3
- Wenn Sie auf die Eingabe klicken, wird das
focusinnicht auf der Eingabe, sondern auf dem Hostknoten selbst angezeigt. Es wurde neu ausgerichtet.
Ereignisse, die immer gestoppt werden
Die folgenden Ereignisse überschreiten nie die Schattengrenze:
- abort
- Fehler
- auswählen
- Ändern
- load
- Zurücksetzen
- resize
- scroll
- selectstart
Fazit
Ich hoffe, Sie stimmen mir zu, dass Shadow DOM unglaublich leistungsstark ist. Zum ersten Mal haben wir eine richtige Kapselung ohne den zusätzlichen Aufwand von <iframe>s oder anderen älteren Techniken.
Shadow DOM ist sicherlich ein komplexes Thema, aber es lohnt sich, es der Webplattform hinzuzufügen. Nimm dir etwas Zeit dafür. Lernen. Fragen stellen
Weitere Informationen finden Sie im Einführungsartikel Shadow DOM 101 von Dominic und in meinem Artikel Shadow DOM 201: CSS & Styling.