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:
- „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ügemarkeroot2.innerHTML = '<shadow></shadow><div>Root 2 FTW</div>';
. - 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.
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 einmouseout
ausgelöst.
Aktion 2 spielen
- Auf dem Host wird ein
mouseout
(ganz am Ende) angezeigt. Normalerweise würdenmouseout
-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.