ShadowDOM

What is the Shadown DOM? Shadown DOM is one of the key interface implementations of Web Components technology. The Shadow DOM interface can attach a hidden, independent DOM to an element. Operating the Shadown DOM is the same as operating a normal DOM. Any DOM element can be added inside the Shadow DOM. Elements inside the Shadow DOM will never affect elements outside it (except for focus-within). For example, adding a style element will only affect elements inside the Shadow DOM.

Create Shadow DOM

Use the element.shadow () method to attach a shadow DOM to the specified Element and return a reference to shadow Root. The DOM tree after the Element has attached the shadow DOM looks like this:

  • Shadow host: A regular DOM node to which the Shadow DOM is attached.
  • Shadow Tree: Shadow DOM Internal DOM tree.
  • Shadow boundary: The place where the Shadow DOM ends and where the regular DOM begins.
  • Shadow root: indicates the root node of Shadow tree

Code demo:

<! DOCTYPEhtml>
<html>
    <body>
        <div class="shadow-host"></div>
        <! -- js part -->
        <script>
            const shadowHost = document.querySelector('.shadow-host');
            const shadowRoot = shadowHost.attachShadow({mode: 'closed'})
        </script>
    </body>
</html>
Copy the code

Open the document in a browser and you can see that #shadow-root(closed) is added to the div.shadow-host element.

{mode: ‘closed’}} {mode: ‘closed’}}

  • Closed: The shadowRoot element cannot be accessed by external JS.Element.shadowRootnull
  • Open: The shadowRoot element can be accessed by external JS, i.e. throughElement.shadowRootProperty to access this node

At this point, shadow-root is still an empty node, and some nodes need to be added to it

<! DOCTYPEhtml>
<html>
    <body>
        <div class="shadow-host"></div>
        <! -- js part -->
        <script>
            const shadowHost = document.querySelector('.shadow-host');
            const shadowRoot = shadowHost.attachShadow({mode: 'closed'})
            const shadowDiv = document.createElement('div');
            shadowDiv.classList.add('shadow-div');
            shadowDiv.innerText = 'i am shadow div'
            shadowRoot.appendChild(shadowDiv)
        </script>
    </body>
</html>
Copy the code

The page is as follows:

Note:

When a Shadow DOM is attached to a normal element, all the normal DOM elements it originally contains are invalidated. Both Shadow DOM and normal DOM cannot exist under a normal element. While you can still get these common elements using JS, the browser will not render them into the page.

Ex. :

<! DOCTYPEhtml>
<html>
    
<body>
    <div class="shadow-host"><span class="origin-span">I'm a normal DOM element</span></div>
    <button style="margin-top: 50px;">Add the Shadow of the DOM</button>
    <! -- js part -->
    <script>
        const btn = document.querySelector('button');
        btn.onclick = attachShadow;
        function attachShadow() {
            const shadowHost = document.querySelector('.shadow-host');
            const shadowRoot = shadowHost.attachShadow({ mode: 'closed' });
            const shadowDiv = document.createElement('div');
            shadowDiv.classList.add('shadow-div');
            shadowDiv.innerText = 'I'm a shadow div';
            shadowRoot.appendChild(shadowDiv);
            console.log('Attached Shadow DOM')
            // Get an existing normal DOM element
            console.log('Normal DOM > origin-span:'.document.querySelector('.origin-span'))}</script>
</body>

</html>
Copy the code

DOM element attached Shadow DOM:

Isolation features of Shadow DOM

As mentioned above, elements inside the Shadow DOM never affect elements outside it (except :focus-within). Let’s explore how elements in the Shadow DOM relate to ordinary external DOM elements.

focus-within

Focus-within: is a CSS pseudo-class that indicates that an element gets focus, or that its descendants get focus. That is, even for a Shadow DOM, when it gets focus, the focus-within on the external parent DOM element takes effect. Verify this with code:

<! DOCTYPEhtml>
<html>
<head>
    <meta http-equiv="X-UA-Compatiple" content="IE=edge">
    <title>test</title>
    <style>
        .shadow-host-parent:focus-within {
            padding: 10px;
            background-color: cyan;
        }
        .shadow-host {
            padding: 5px;
            border: 1px dashed black;
        }
        .shadow-host:focus-within {
            background-color: cornflowerblue;
        }
    </style>
</head>
<body>
    <div class="shadow-host-parent">
        <div class="shadow-host"></div>
    </div>
    <! -- js part -->
    <script>
        attachShadow();
        function attachShadow() {
            const shadowHost = document.querySelector('.shadow-host');
            const shadowRoot = shadowHost.attachShadow({ mode: 'closed' });
            const shadowInput = document.createElement('input');
            shadowInput.value = 'input'
            shadowRoot.appendChild(shadowInput);
        }
    </script>
</body>
</html>
Copy the code

Style isolation

First the conclusion:

  1. External CSS (including style and external link resources) is applied inShadow HostAttributes in the element’s style (including inherited styles) that can be inherited are inherited by elements in the Shadow DOM, and cannot be directly checked by selectorsShadow DOMApplies a style to the element.
  2. In the Shadow of the DOMstyleorThe introduction of CSSThere is no effect on external elements

As Shadow DOM naturally has good style isolation characteristics, it is also used as a solution to achieve style isolation in the micro front-end framework Qiankun.

Example 1: There is no external way to change the style of elements in the Shadow DOM through the class selector

As you can see, the className pure-text is also available, but only for the external DOM.

Example 2: Styles on the Shadow Host element can be inherited

To change the example slightly, I’ll add a color style to the shadow-host element

Example 3: Shadow DOM internal application styles cannot be changed to affect external elements

Introducing CSS files in Shadom:

shadow.css

.btn-primary {
    background-color: #1890ff;
    padding: 5px 10px;
    color: #fff;
}
Copy the code

HTML:

<! DOCTYPEhtml>
<html>
<head></head>
<body>
    <div class="shadow-host"></div>
    <div style="margin-top:20px"><span class="btn-primary">click Me!</span></div>
    <! -- js part -->
    <script>attachShadow(); function attachShadow() { const shadowHost = document.querySelector('.shadow-host'); const shadowRoot = shadowHost.attachShadow({ mode: 'closed' }); Shadow.css shadowroot. innerHTML = '<head><link rel="stylesheet" href="./shadow.css" type="text/css"></link></head>
            <span class="btn-primary">I'm a Button in Shadow DOM, click Me!</span>`;
        }
    </script>
</body>
</html>
Copy the code

The result is as follows, only for elements in the Shadow DOM:

Event propagation

The element event propagation mechanism inside the Shadow DOM is the same as normal DOM, and events can be safely registered. But the target attribute in the event is a little different.

The value of the event. target attribute is the element that currently triggers the Event. When the Event is triggered by Shadow DOM, the Event.

html:

<! DOCTYPEhtml>
<html>
<body>
    <div class="shadow-host-parent" style="padding: 20px">
        <div class="shadow-host" style="border:1px solid #000; padding:10px"></div>
    </div>
    <! -- js part -->
    <script>
        attachShadow();
        function attachShadow() {
            const shadowHostParent = document.querySelector('.shadow-host-parent')
            const shadowHost = document.querySelector('.shadow-host');
            const shadowRoot = shadowHost.attachShadow({ mode: 'open' });
            const shadowDiv = document.createElement('div');
            const shadowSpan = document.createElement('span');
            shadowSpan.innerText = 'i am shadow span';
            shadowDiv.appendChild(shadowSpan);
            shadowRoot.appendChild(shadowDiv);

            shadowSpan.onclick = function (e) {
                console.log('Event-triggered target:', e.target)
                console.log('Shadow Span has been clicked! ');
                console.log(' ')
            }
            shadowDiv.onclick = function (e) {
                console.log('Event-triggered target:', e.target)
                console.log('Bubble to shadow Span's parent div! ');
                console.log(' ')
            }
            shadowHost.onclick = (e) = > {
                console.log('Event-triggered target:', e.target)
                console.info('Bubble to shadow host! ');
                console.log(' ')
            }
            shadowHostParent.onclick = (e) = > {
                console.log('Event-triggered target:', e.target)
                console.log('Bubbles to shadow host's parent element! ');
                console.log(' ')}document.onclick = function (e) {
                console.log('Event-triggered target:', e.target)
                console.log('Bubbles to Document! ');
                console.log(' ')}}</script>
</body>
</html>
Copy the code

When clicked, the output is as follows:

When the Event is propagated within the Shadow DOM, the event. target accurately reflects the element that triggered the Event. Once the Event propagated reaches Shadow Host, the event. target changes to the Shadow Host element in subsequent events. Perhaps this is also a protection mechanism, as elements inside the Shadow DOM cannot be accessed externally.

I also tested executing Event functions during the Event capture phase, and the event.target behavior was consistent with the bubbling phase.

Therefore, it should be noted that if the Event delegation mechanism is adopted outside the Shadow DOM, the target Event elements cannot be accurately determined through event. target.

Refer to the link

MDN Shadow DOM