Einführung in Shadow DOM
German (Deutsch) translation by Tatsiana Bochkareva (you can also view the original English article)
Nehmen Sie eine moderne Webseite und Sie bemerken, dass sie ausnahmslos einen Content hat, die aus verschiedenen Quellen zusammengefügt wurden. Es kann die Social-Sharing-Widgets von Twitter oder Facebook oder ein Youtube Video-Wiedergabe-Widget enthalten, es kann eine personalisierte Werbung von einem Ad-Server liefern oder es kann einige Hilfsskripte oder -stile aus einer über CDN gehosteten Drittanbieter-Bibliothek usw. enthalten. Und wenn alles HTML-basiert ist (wie es heutzutage bevorzugt wird), besteht eine hohe Wahrscheinlichkeit von Kollisionen zwischen Markups, Skripten oder Stilen, die aus verschiedenen Quellen bereitgestellt werden. Im Allgemeinen werden Namespaces verwendet, um diese Kollisionen zu verhindern, die das Problem bis zu einem gewissen Grad lösen. Sie bieten jedoch keine Kapselung.
Die Kapselung ist eine der Säulen, auf denen das Paradigma der objektorientierten Programmierung basiert, und wird normalerweise verwendet, um die interne Repräsentation eines Objekts von außen einzuschränken.
Wenn wir auf unser Problem zurückkommen, können wir den JavaScript-Code sicherlich mithilfe von Abschlüssen oder mithilfe des Modulmusters kapseln. Können wir dies jedoch auch für unser HTML-Markup tun? Stellen Sie sich vor, wir müssen ein UI-Widget erstellen. Können wir die Implementierungsdetails unseres Widgets vor dem JavaScript- und CSS-Code verbergen, der auf der Seite enthalten ist, die unser Widget verwendet? Können wir alternativ verhindern, dass der konsumierende Code die Funktionalität oder das Erscheinungsbild unseres Widgets beeinträchtigt?
Shadow DOM zur Rettung
Die einzige existierende Lösung, die eine Grenze zwischen dem von Ihnen geschriebenen Code und dem verbrauchenden Code schafft, ist hässlich - und verwendet einen sperrigen und restriktiven iFrame, der weitere Probleme mit sich bringt. Sind wir also gezwungen, uns immer an diesen Ansatz anzupassen?
Nicht länger! Shadow DOM bietet uns eine elegante Möglichkeit, den normalen DOM-Teilbaum mit einem speziellen Dokumentfragment zu überlagern, das einen weiteren Teilbaum von Knoten enthält, die für Skripte und Stile uneinnehmbar sind. Das Interessante ist, dass es nichts Neues ist! Verschiedene Browser haben diese Methode bereits verwendet, um native Widgets wie Datum, Schieberegler, Audio-, Videoplayer usw. zu implementieren.
Aktivieren von Shadow DOM
Zum Zeitpunkt dieses Schreibens unterstützt die aktuelle Version von Chrome(v29) die Überprüfung von Shadow DOM mit Chrome DevTools. Öffnen Sie Devtools und klicken Sie auf die Zahnradschaltfläche unten rechts auf dem Bildschirm, um das Einstellungsfenster zu öffnen. Scrollen Sie ein wenig nach unten und Sie sehen ein Kontrollkästchen zum Anzeigen von Shadow DOM.


Nachdem wir unseren Browser aktiviert haben, können Sie die Interna des Standard-Audio-Players überprüfen. Tippen Sie einfach:
1 |
<audio width="300" height="32" src="http://developer.mozilla.org/@api/deki/files/2926/=AudioTest_(1).ogg" autoplay="autoplay" controls="controls"> |
2 |
Your browser does not support the HTML5 Audio. |
3 |
</audio>
|
In Ihr HTML-Markup. In den unterstützten Browsern wird der folgende native Audio-Player angezeigt:

Überprüfen Sie nun das gerade erstellte Audio-Player-Widget.


Beeindruckend! Es zeigt die interne Darstellung des Audio-Players, die ansonsten ausgeblendet wurde. Wie wir sehen können, verwendet das Audioelement ein Dokumentfragment, um den internen Inhalt des Widgets zu speichern, und hängt diesen an das Containerelement (das als Shadow Host bezeichnet wird) an.
Shadow Host und Shadow Root
- Shadow Host: ist das DOM-Element, das den
Shadow DOM-Teilbaum hostet, oder es ist der DOM-Knoten, der die Shadow Root enthält. - Schadow Root: ist Root des DOM-Teilbaums, der die Schatten-DOM-Knoten enthält. Es ist ein spezieller Knoten, der die Grenze zwischen den normalen DOM-Knoten und den Schatten-DOM-Knoten erstellt. Es ist diese Grenze, die die Shadow-DOM-Knoten aus jedem JavaScript- oder CSS-Code auf der konsumierenden Seite kapselt.
- Shadow DOM: Ermöglicht die Zusammenstellung mehrerer DOM-Teilbäume zu einem größeren Baum. Das Befolgen von Bildern aus dem W3C-Arbeitsentwurf erklärt das Konzept der Überlagerung der Knoten am besten. So sieht es aus, bevor der Inhalt der Schattenwurzel an das Schattenhost-Element angehängt wird:


Beim Rendern ersetzt der Shadow Tree den Inhalt des Schattenhosts.


Dieser Prozess der Überlagerung der Knoten wird häufig als Zusammensetzung bezeichnet.
- Shadow Boundary: Wird im obigen Bild durch die gepunktete Linie gekennzeichnet. Dies bezeichnet die Trennung zwischen der normalen DOM-Welt und der Shadow-DOM-Welt. Die Skripte von beiden Seiten können diese Grenze nicht überschreiten und auf der anderen Seite Chaos verursachen.
Hallo Shadow DOM World
Genug geplaudert Ich sage: Machen wir uns die Hände schmutzig, indem wir Code schreiben. Angenommen, wir haben das folgende Markup, das eine einfache Begrüßungsnachricht anzeigt.
1 |
<div id="welcomeMessage">Welcome to My World</div> |
Fügen Sie den folgenden JavaScript-Code hinzu oder verwenden Sie diese Fiddle:
1 |
var shadowHost = document.querySelector("#welcomeMessage");
|
2 |
var shadowRoot = shadowHost.webkitCreateShadowRoot(); |
3 |
shadowRoot.textContent = "Hello Shadow DOM World"; |
Hier erstellen wir mit der Funktion webkitCreateShadowRoot() ein Shadow Root, hängen es an einen Shadow Host an und ändern dann einfach den Inhalt.
Beachten Sie das herstellerspezifische Präfix-webkit vor dem Funktionsnamen. Dies zeigt an, dass diese Funktionalität derzeit nur in einigen Webkit-basierten Browsern unterstützt wird.
Wenn Sie dieses Beispiel in einem unterstützten Browser ausführen, wird "Hallo Schatten-DOM-Welt" anstelle von "Willkommen in meiner Welt" angezeigt, da die Schatten-DOM-Knoten die normalen Knoten überschattet haben.
Haftungsausschluss: Wie einige von Ihnen vielleicht bemerken, mischen wir das Markup mit Skripten, was im Allgemeinen nicht empfohlen wird und Shadow DOM keine Ausnahme darstellt. Wir haben bewusst darauf verzichtet, Vorlagen so früh im Spiel zu verwenden, um Verwirrung zu vermeiden. Andernfalls bietet Shadow DOM eine elegante Lösung für dieses Problem, und wir werden es bald erreichen.
Schattengrenze respektieren
Wenn Sie versuchen, mit JavaScript auf den Inhalt des gerenderten Baums zuzugreifen, gehen Sie wie folgt vor:
1 |
var shadowHost = document.querySelector("#welcomeMessage");
|
2 |
var shadowRoot = shadowHost.webkitCreateShadowRoot(); |
3 |
shadowRoot.textContent = "Hello Shadow DOM World"; |
4 |
|
5 |
console.log(shadowHost.textContent); |
6 |
// Prints "Welcome to My World" as the shadow DOM nodes are encapsulated and cannot be accessed by JavaScript |
Sie erhalten den ursprünglichen Inhalt "Willkommen in meiner Welt" und nicht den Inhalt, der tatsächlich auf der Seite gerendert wird, da der Shadow DOM-Baum aus beliebigen Skripten gekapselt ist. Dies bedeutet auch, dass das Widget, das Sie mit Shadow DOM erstellen, vor unerwünschten / widersprüchlichen Skripten geschützt ist, die bereits auf der Seite vorhanden sind.
Styles-Kapselung
Ebenso ist es jedem CSS-Selektor verboten, die Schattengrenze zu überschreiten. Überprüfen Sie den folgenden Code, in dem wir den Listenelementen rote Farbe zugewiesen haben. Dieser Stil wird jedoch nur auf die Knoten angewendet, die Teil der übergeordneten Seite sind, und die Listenelemente, die Teil von Shadow Root sind, sind von diesem Stil nicht betroffen.
1 |
<div class="outer"> |
2 |
<div id="welcomeMessage">Welcome to My World</div> |
3 |
<div class="normalTree">Sample List |
4 |
<ul>
|
5 |
<li>Item 1</li> |
6 |
<li>Item 2</li> |
7 |
</ul>
|
8 |
</div>
|
9 |
</div>
|
10 |
<style>
|
11 |
div.outer li { |
12 |
color: red; |
13 |
}
|
14 |
div.outer{ |
15 |
border: solid 1px; padding: 1em; |
16 |
}
|
17 |
</style>
|
18 |
<script type="text/javascript"> |
19 |
var shadowHost = document.querySelector("#welcomeMessage"); |
20 |
var shadowRoot = shadowHost.webkitCreateShadowRoot(); |
21 |
shadowRoot.innerHTML = ["<div class='shadowChild'>", |
22 |
"Shadow DOM offers us Encapsulation from", |
23 |
"<ul>", |
24 |
"<li>Scripts</li>", |
25 |
"<li>Styles</li>", |
26 |
"</ul>", |
27 |
"</div>" |
28 |
].join(',').replace(/,/g,""); |
29 |
</script>
|
Sie können den Code in Aktion auf Fiddle sehen. Diese Kapselung gilt auch dann, wenn wir die Richtung der Durchquerung umkehren. Alle Stile, die im Shadow DOM definiert sind, wirken sich nicht auf das übergeordnete Dokument aus und bleiben nur für das Shadow Root gültig. In dieser Fiddle finden Sie ein Beispiel, in dem wir die blaue Farbe auf Listenelemente in Shadow DOM anwenden, die Listenelemente des übergeordneten Dokuments jedoch nicht betroffen sind.
Es gibt hier jedoch eine bemerkenswerte Ausnahme; Shadow DOM gibt uns die Flexibilität, den Shadow Host zu formatieren, den DOM-Knoten, der das Shadow DOM enthält. Im Idealfall liegt es außerhalb der Schattengrenze und ist nicht Teil von Shadow Root. Mit der @host-Regel können Sie jedoch die Stile angeben, die auf Shadow Host angewendet werden können, da wir die Begrüßungsnachricht im folgenden Beispiel gestaltet haben.
1 |
<div id="welcomeMessage">Welcome to My World</div> |
2 |
<script type="text/javascript"> |
3 |
var shadowHost = document.querySelector("#welcomeMessage"); |
4 |
var shadowRoot = shadowHost.webkitCreateShadowRoot(); |
5 |
shadowRoot.innerHTML = ["<style>", |
6 |
"@host{ ", |
7 |
"#welcomeMessage{ ", |
8 |
"font-size: 28px;", |
9 |
"font-family:cursive;", |
10 |
"font-weight:bold;", |
11 |
"}", |
12 |
"}", |
13 |
"</style>", |
14 |
"<content select=''></content>" |
15 |
].join(',').replace(/,/g,""); |
16 |
</script>
|
Überprüfen Sie diese Fiddle, während wir die Begrüßungsnachricht des Shadow Hosts mit den in Shadow DOM definierten Stilen gestalten.
Erstellen von Style-Hooks
Als Widget-Entwickler möchte ich möglicherweise, dass der Benutzer meines Widgets bestimmte Elemente formatieren kann. Dies ist möglich, indem ein Loch mit benutzerdefinierten Pseudoelementen in die Schattengrenze gesteckt wird. Dies ähnelt dem Erstellen von Style-Hooks durch einige Browser, damit der Entwickler einige interne Elemente eines nativen Widgets formatieren kann. Um beispielsweise den Daumen und die Spur des nativen Schiebereglers zu formatieren, können Sie den ::-webkit-slider-thumb und den ::webkit-slider-runnable-track wie folgt verwenden:
1 |
input[type=range]{ |
2 |
-webkit-appearance:none; |
3 |
}
|
4 |
input[type=range]::-webkit-slider-thumb { |
5 |
-webkit-appearance:none; |
6 |
height:12px; |
7 |
width:12px; |
8 |
border-radius:6px; |
9 |
background:yellow; |
10 |
position:relative; |
11 |
top:-5px; |
12 |
}
|
13 |
input[type=range]::-webkit-slider-runnable-track { |
14 |
background:red; |
15 |
height:2px; |
16 |
}
|
Gabel diese Fiddle und wende deine eigenen Stile darauf an!
Event-Re-Targeting
Wenn ein Ereignis, das von einem der Knoten in Shadow DOM stammt, die Shadow Boundary überschreitet, wird erneut auf den Shadow Host verwiesen, um die Kapselung aufrechtzuerhalten. Betrachten Sie den folgenden Code:
1 |
<input id="normalText" type="text" value="Normal DOM Text Node" /> |
2 |
<div id="shadowHost"></div> |
3 |
<input id="shadowText" type="text" value="Shadow DOM Node" /> |
4 |
<script type="text/javascript"> |
5 |
var shadowHost = document.querySelector('#shadowHost'); |
6 |
var shadowRoot = shadowHost.webkitCreateShadowRoot(); |
7 |
var template = document.querySelector('template'); |
8 |
shadowRoot.appendChild(template.content.cloneNode(true)); |
9 |
template.remove(); |
10 |
document.addEventListener('click', function(e) { |
11 |
console.log(e.target.id + ' clicked!'); |
12 |
});
|
13 |
</script>
|
Es rendert zwei Texteingabeelemente, eines über das normale DOM und eines über das Schatten-DOM, und wartet dann auf ein click ereignis im document. Wenn Sie nun auf die zweite Texteingabe klicken, stammt das Ereignis aus dem Shadow DOM. Wenn es die Schattengrenze überschreitet, wird das Ereignis so geändert, dass das Zielelement anstelle der Texteingabe <input> in das <div> -Element von Shadow Host geändert wird . Wir haben hier auch ein neues <template> -Element eingeführt. Dies ähnelt konzeptionell clientseitigen Vorlagenlösungen wie Lenker und Unterstrich, ist jedoch nicht so weiterentwickelt und bietet keine Browserunterstützung. Die Verwendung von Vorlagen ist jedoch der ideale Weg, um Shadow DOM zu schreiben, anstatt Skript-Tags zu verwenden, wie dies bisher in diesem Artikel getan wurde.
Trennung von Bedenken
Wir wissen bereits, dass es immer eine gute Idee ist, den tatsächlichen Inhalt von der Präsentation zu trennen. Shadow DOM sollte keine Inhalte einbetten, die dem Benutzer endgültig angezeigt werden sollen. Der Inhalt sollte immer auf der Originalseite vorhanden sein und nicht in der Shadow DOM-Vorlage versteckt sein. Wenn die Komposition erfolgt, sollte dieser Inhalt in geeignete Einfügepunkte projiziert werden, die in der Vorlage des Shadow DOM definiert sind. Lassen Sie uns das Hello World-Beispiel unter Berücksichtigung der obigen Trennung neu schreiben - ein Live-Beispiel finden Sie auf Fiddle.
1 |
<div id="welcomeMessage">Welcome to Shadow DOM World</div> |
2 |
<script type="text/javascript"> |
3 |
var shadowRoot = document.querySelector("#welcomeMessage").webkitCreateShadowRoot(); |
4 |
var template = document.querySelector("template"); |
5 |
shadowRoot.appendChild(template.content); |
6 |
template.remove(); |
7 |
</script>
|
Beim Rendern der Seite wird der Inhalt des Schattenhosts an die Stelle projiziert, an der das Element <content> angezeigt wird. Dies ist ein sehr vereinfachtes Beispiel, bei dem <content> während der Komposition alles im Shadow Host aufnimmt. Es kann jedoch sehr selektiv sein, den Inhalt von Shadow Host mithilfe des unten gezeigten select-Attributs auszuwählen
1 |
<div id="outer">How about some cool demo, eh ? |
2 |
<div class="cursiveButton">My Awesome Button</div> |
3 |
</div>
|
4 |
<button>
|
5 |
Fallback Content |
6 |
</button>
|
7 |
<style>
|
8 |
button{ |
9 |
font-family: cursive; |
10 |
font-size: 24px; |
11 |
color: red; |
12 |
}
|
13 |
</style>
|
14 |
<script type="text/javascript"> |
15 |
var shadowRoot = document.querySelector("#outer").webkitCreateShadowRoot(); |
16 |
var template = document.querySelector("template"); |
17 |
shadowRoot.appendChild(template.content.cloneNode(true)); |
18 |
template.remove(); |
19 |
</script>
|
Schauen Sie sich die Live-Demo an und spielen Sie damit, um das Konzept der Einfügepunkte und Projektionen besser zu verstehen.
Webkomponenten
Wie Sie vielleicht bereits wissen, ist Shadow DOM Teil der Web Components Spec, die andere nützliche Funktionen bietet, wie zum Beispiel:
- Vorlagen - werden verwendet, um inerte Markups zu speichern, die zu einem späteren Zeitpunkt verwendet werden sollen. Mit inert meinen wir, dass nicht alle Bilder im Markup heruntergeladen werden und die enthaltenen Skripte erst vorhanden sind, wenn der Inhalt der Vorlage tatsächlich Teil der Seite wird.
- Dekorateure - werden verwendet, um die auf CSS-Selektoren basierenden Vorlagen anzuwenden, und können daher als Dekoration der vorhandenen Elemente angesehen werden, indem ihre Präsentation verbessert wird.
- HTML-Importe - bietet uns die Möglichkeit, andere HTML-Dokumente in unserem Dokument wiederzuverwenden, ohne explizit XHR-Aufrufe durchführen und Ereignishandler dafür schreiben zu müssen.
- Benutzerdefinierte Elemente - Ermöglicht die Definition neuer HTML-Elementtypen, die dann deklarativ im Markup verwendet werden können. Wenn Sie beispielsweise ein eigenes Navigations-Widget erstellen möchten, definieren Sie Ihr Navigationselement, das von HTMLElement erbt, und stellen bestimmte Lebenszyklus-Rückrufe bereit, die bestimmte Ereignisse wie Aufbau, Änderung, Zerstörung des Widgets implementieren und dieses Widget einfach in Ihrem Markup verwenden als
<myAwesomeNavigation attr1="value"..></myAwesomeNavigation>Benutzerdefinierte Elemente geben uns also im Wesentlichen die Möglichkeit, die gesamte Magie von Shadow DOM zu bündeln, die internen Details zu verbergen und alles zusammen zu packen.
Ich werde nicht viel über andere Aspekte der Webkomponenten-Spezifikation in diesem Artikel plappern, aber es würde uns gut tun, daran zu denken, dass sie es uns gemeinsam ermöglichen, wiederverwendbare UI-Widgets zu erstellen, die in Aussehen und Verhalten über Browser hinweg portierbar sind und von allen vollständig gekapselt werden Skripte und Stile der konsumierenden Seite.
Abschluss
Die Webkomponenten-Spezifikation ist in Arbeit und der enthaltene Beispielcode, der heute funktioniert, funktioniert möglicherweise nicht in einer späteren Version. In früheren Texten zu diesem Thema wird beispielsweise die webkitShadowRoot() -Methode verwendet, die nicht mehr funktioniert. Verwenden Sie stattdessen createWebkitShadowRoot(), um eine Schattenwurzel zu erstellen. Wenn Sie dies verwenden möchten, um einige coole Demos mit Shadow DOM zu erstellen, ist es immer am besten, sich für Einzelheiten auf die Spezifikation zu beziehen.
Derzeit wird es nur von Chrome und Opera unterstützt, daher wäre ich vorsichtig, wenn ich Shadow DOM in meine Produktionsinstanz aufnehmen würde. Da Google jedoch Polymer herausbringt, das auf Webkomponenten und Polyfills basiert, die Shadow DOM nativ unterstützen, ist dies der Fall sicherlich etwas, mit dem sich jeder Webentwickler die Hände schmutzig machen muss.
Sie können auch über die neuesten Ereignisse in Shadow DOM auf dem Laufenden bleiben, indem Sie diesem Google+ Kanal folgen. Schauen Sie sich auch das Shadow DOM Visualizer-Tool an, mit dem Sie visualisieren können, wie Shadow DOM im Browser gerendert wird.



