⬆️ ⬇️

Shadow dom

Reference to standard: www.w3.org/TR/2013/WD-shadow-dom-20130514



So, what is the shadow DOM:

Shadow DOM (or the shadow model of the document) is the part of the document that implements encapsulation in the DOM tree. It (the shadow model) is part of the document and is embedded directly into the page.

To simplify debugging shadow DOM, in chrome, you can enable the display in the web inspector (Settings - General - Show shadow DOM).



It should be noted that in the standard, implemented encapsulation is called functional, since shadow DOM is embedded in the document and is one of its many parts that work "independently" (more or less independently) from each other. Accordingly, when designing the implementation, it was necessary to establish functional boundaries in the document tree in order to somehow operate with a set of such “independent” fragments. To solve the problem of encapsulation, a new abstraction was introduced - shadow DOM, which allows to create several DOM trees within one parent tree and a document describing it was developed.



')

The child tree is located inside some element on the page. The functional boundaries between the main document tree and the shadow tree are called shadow boundaries. The element that places the shadow tree in itself is called the shadow host, and the root of the shadow tree, respectively, is called the shadow root.







During rendering, the shadow tree takes the place of the contents of the shadow host (element).



An example implementation in chromium:



<div id="shadow-host"></div> 




 var shadowHost = document.querySelector("#shadow-host"), shadowRoot = shadowHost.webkitCreateShadowRoot(); 








Insertion points


For the composition of descendants, the shadow host and the shadow tree use insertion points. Insertion points locate the descendants of the shadow host in the shadow tree. When rendering a shadow tree, the descendants are projected into this place. The mechanism that determines which descendants of the shadow host will be projected at the insertion point is called distribution.



Implementation:



 <div id="shadow-host"> <span>Hi shadow DOM!</span> </div> 




 var shadowHost = document.querySelector("#shadow-host"), shadowRoot = shadowHost.webkitCreateShadowRoot(), content = document.createElement("content"); content.select = "span"; //     shadow host shadowRoot.appendChild(content); 








Pseudo-element :: distributed ()


:: distributed (selector) is a functional pseudo-element that takes a relative selector as an argument. It represents the relationship between the insertion point in the shadow tree and the element carried in the insertion point.



Implementation (chrome canary only):



 <html> <head> <script> function onLoad() { var shadowHost = document.querySelector("#shadow-host"), shadowRoot = shadowHost.webkitCreateShadowRoot(); shadowRoot.innerHTML = document.querySelector("template").innerHTML; } </script> </head> <body onload="onLoad()"> <div id="shadow-host"> <span>Hi shadow DOM!</span> </div> <template> <style> content::-webkit-distributed(span) { color: red !important; } </style> <content></content> </template> </body> </html> 




One shadow host can hold several shadow trees - they will be displayed in the order of their addition. Such a set of trees is called shadow stack. An “older” shadow tree can also be transferred to another shadow tree with the shadow insertion point.



 <html> <head> <script> function onLoad() { var shadowHost = document.querySelector("#shadow-host"), firstShadowRoot = shadowHost.webkitCreateShadowRoot(), secondShadowRoot = shadowHost.webkitCreateShadowRoot(); firstShadowRoot.innerHTML = document.querySelector("#template-1").innerHTML; secondShadowRoot.innerHTML = document.querySelector("#template-2").innerHTML; } </script> </head> <body onload="onLoad()"> <div id="shadow-host"> <span>Hi shadow DOM!</span> </div> <template id="template-1"> <div>root 1</div> </template> <template id="template-2"> <div>root 2</div> <shadow></shadow> </template> </body> </html> 




Reprojection


Reprojection is a situation in which the first shadow tree already has an insertion point, and the second shadow tree has a shadow insetion point, while the content taken from the shadow host is first projected in the first shadow tree and then in the second.



 <html> <head> <script> function onLoad() { var shadowHost = document.querySelector("#shadow-host"), firstShadowRoot = shadowHost.webkitCreateShadowRoot(), secondShadowRoot = shadowHost.webkitCreateShadowRoot(); firstShadowRoot.innerHTML = document.querySelector("#template-1").innerHTML; secondShadowRoot.innerHTML = document.querySelector("#template-2").innerHTML; } </script> </head> <body onload="onLoad()"> <div id="shadow-host"> <span>Hi shadow DOM!</span> </div> <template id="template-1"> <div>root 1</div> <content select="span"></content> </template> <template id="template-2"> <div>root 2</div> <shadow></shadow> </template> </body> </html> 




Pseudo-elements (in the context of shadow DOM)


The author of the standard writes:

Abbreviation of the shadow of the tree.


In certain situations, the author of the shadow tree will want to assign one or more elements from the shadow tree as an abstract abstraction that provides additional information about the content of the shadow tree.



What I understand as the ability to use css selectors outside the shadow tree to access the elements inside it:

 <html> <head> <script> function onLoad() { var shadowHost = document.querySelector("#shadow-host"), shadowRoot = shadowHost.webkitCreateShadowRoot(); shadowRoot.innerHTML = document.querySelector("template").innerHTML; } </script> <style> div::x-thumb { width: 10px; height: 10px; background: black; } </style> </head> <body onload="onLoad()"> <div id="shadow-host"></div> <template> <div pseudo="x-thumb"></div> </template> </body> </html> 




Developments


Some events are passed through the shadow boundary, some are not. The exception is mutation events - they should not occur at all in the shadow tree and, accordingly, pass through the shadow boundary. When the event passes through the shadow boundary, its event.target changes to maintain encapsulation.

Here is an interesting example:

 <html> <head> <script> function onLoad() { var shadowHost = document.querySelector("#shadow-host"), shadowRoot = shadowHost.webkitCreateShadowRoot(); shadowRoot.innerHTML = document.querySelector("template").innerHTML; shadowHost.addEventListener("mouseout", function(e) { console.log("mouse out", e.target); }); } </script> <style> #shadow-host { width: 100px; height: 100px; background: blue; } #outer-element { width: 100%; height: 20px; background: red; } </style> </head> <body onload="onLoad()"> <div id="shadow-host"> <div id="outer-element"></div> </div> <template> <div id="first-inner-element"></div> <div id="second-inner-element"></div> <content></content> <style> #first-inner-element { width: 100px; height: 20px; background: green; position: absolute; top: 140px; } #second-inner-element { width: 100px; height: 20px; background: black; margin-bottom: 40px; } </style> </template> </body> </html> 




Events of the projected element float into the shadow host, as if it is still directly inside the shadow host. The first-inner-element events do not pop up in the shadow host, unlike the second-inner-element, which is absolutely positioned and moved out of the shadow host (and the event.target has changed).



Styles


There are two methods to manipulate the shadow tree styles:



shadowRoot.resetStyleInheritance (false by default)

Resets the inheritance of styles for shadow tree (styles not applied to shadow tree from the outside).



shadowRoot.applyAuthorStyles (false by default)

Applies the styles of the author (main) document.



 <html> <head> <script> function onLoad() { var shadowHost = document.querySelector("#shadow-host"), firstShadowRoot = shadowHost.webkitCreateShadowRoot(); var secondShadowRoot = shadowHost.webkitCreateShadowRoot(); secondShadowRoot.resetStyleInheritance = true; secondShadowRoot.applyAuthorStyles = true; firstShadowRoot.innerHTML = document.querySelector("#template-1").innerHTML; secondShadowRoot.innerHTML = document.querySelector("#template-2").innerHTML; } </script> <style> * { font-style: italic; } </style> </head> <body onload="onLoad()"> <div id="shadow-host"></div> <template id="template-1"> <style> * { color: red; font-weight: bold; } </style> <div>root 1</div> </template> <template id="template-2"> <div>root 2</div> <shadow></shadow> </template> </body> </html> 




Total



We can say that some "encapsulation" for html was not enough. This opens up great opportunities for creating and templating various previously prepared widgets on a page. What is surprising is the lack of encapsulation of JavaScript code inside the widgets, although it would seem rather logical to me.

Source: https://habr.com/ru/post/180377/



All Articles