3. Crazy geek


https://blog.logrocket.com/un…


This article first send WeChat messages public number: front-end pioneer welcome attention, every day to you push fresh front-end technology articles


Shadow Dom is not the villain of a superhero movie, nor is it the dark side of Dom. Shadow DOM is just a way of solving the missing tree encapsulation in the Document Object Model, or DOM for short.

Web pages often use data and widgets from external sources, and if they are not encapsulated, then styles can affect unnecessary parts of the HTML, forcing developers to use specific selectors and! Important rule to avoid style conflicts.

However, these efforts don’t seem to be as effective when writing large programs, and a lot of time is wasted trying to prevent CSS and JavaScript from clashing. The Shadow DOM API aims to solve these problems by providing a mechanism to encapsulate the DOM tree.

Shadow DOM is one of the main techniques for creating Web components, along with custom elements and HTML templates. The specification for Web Components was originally proposed by Google to simplify the development of Web widgets.

While these three technologies are intended to work together, you are free to use each separately. The scope of this tutorial is limited to the shadow DOM.

What is the DOM?

Before delving into how to create a shadow DOM, it’s important to know what the DOM is. The W3C Document Object Model (DOM) provides a platform – and language-independent application programming interface (API) for representing and manipulating information stored in HTML and XML documents.

Using the DOM, programmers can access, add, delete, or change elements and content. DOM treats the Web page as a tree structure, with each branch ending with a node, and each node contains an object that can be modified using a scripting language such as JavaScript. Consider the following HTML documents:

<html>
  <head>
    <title>Sample document</title>
  </head>
  <body>
    <h1>Heading</h1>
    <a href="https://example.com">Link</a>
  </body>
</html>

The DOM representation of this HTML is as follows:

All boxes in this figure are nodes.

The terminology used to describe the DOM part is similar to that used in the real world to describe a family tree:

  • The node one level above a given node is the parent node of the node
  • The node next to a given node is a child node of that node
  • Nodes that have the same parent are siblings
  • All nodes above a given node, including parent and grandfather nodes, are called the ancestor of that node
  • Finally, all nodes under a given node are said to be descendants of that node

The type of a node depends on the type of HTML element it represents. HTML tags are called element nodes. Nested tags form a tree of elements. The text in the element is called a text node. A text node may have no child nodes; you can think of it as a tree leaf.

To access the tree, the DOM provides a set of methods that programmers can use to modify the content and structure of a document. For example, when you write document.createElement(‘p’); , you are using the methods provided by the DOM. Without the DOM, JavaScript would not be able to understand the structure of HTML and XML documents.

The following JavaScript code shows how to use DOM methods to create two HTML elements, nest one inside the other, set the text content, and finally append them to the document body:

const section = document.createElement('section'); const p = document.createElement('p'); p.textContent = 'Hello! '; section.appendChild(p); document.body.appendChild(section);

Here’s the DOM structure generated by running this JavaScript code:

<body> <section> <p>Hello! </p> </section> </body>

What is Shadow Dom?

Encapsulation is a fundamental feature of object-oriented programming that enables programmers to restrict unauthorized access to certain object components.

Under this definition, objects provide interfaces as a means of interacting with their data in the form of public access methods. Thus the internal representation of the object is not directly accessible to the external object.

The Shadow DOM introduces this concept to HTML. It allows you to link hidden, detached DOM to elements, which means you can use the local scope of HTML and CSS. You can now use more generic CSS selectors without worrying about naming conflicts, and styles are no longer leaked or applied to inappropriate elements.

In fact, the Shadow DOM API is exactly what library and widget developers need to separate the HTML structure, style, and behavior from the rest of the code.

The Shadow root is the topmost node in the Shadow tree and is what is attached to the regular DOM node when the Shadow DOM is created. A node with a shadow root associated with it is called a shadow host.

You can attach elements to the shadow root just as you would with normal DOM. Nodes linked to shadow root form a shadow tree. This should be more clear from the chart:

The term light DOM is often used to distinguish between a normal DOM and a shadow DOM. The shadow DOM and light DOM are collectively referred to as the logical DOM. The point at which the light DOM is separated from the shadow DOM is called the shadow boundary. DOM queries and CSS rules cannot reach the other side of the shaded boundary, creating encapsulation.

Create a shadow DOM

To create a shadow DOM, attach the shadow root to the Element using the Element.attachShadow() method:

var shadowroot = element.attachShadow(shadowRootInit);

Consider a simple example:

<div id="host"><p>Default text</p></div> <script> const elem = document.querySelector('#host'); // attach a shadow root to #host const shadowRoot = elem.attachShadow({mode: 'open'}); // create a <p> element const p = document.createElement('p'); // add <p> to the shadow DOM shadowRoot.appendChild(p); // add text to <p> p.textContent = 'Hello! '; </script>

This code appends a shadow DOM tree to the div element with the ID of host. This tree is separate from the actual child elements of the div, and anything added above it will be the local element of the managed element.

Shadow root in Chrome DevTools.

Notice how the existing element in #host is replaced with shadow root. Browsers that do not support shadow DOM will use the default content.

Now, when adding CSS to the main document, the style rules do not affect the shadow DOM:

<div><p>Light DOM</p></div>
<div id="host"></div>
 
<script>
  const elem = document.querySelector('#host');
 
  // attach a shadow root to #host
  const shadowRoot = elem.attachShadow({mode: 'open'});
 
  // set the HTML contained within the shadow root
  shadowRoot.innerHTML = '<p>Shadow DOM</p>';
</script>
 
<style>
  p {color: red}
</style>

Styles defined in the Light DOM cannot cross the shadow boundary. Therefore, only paragraphs in the Light DOM will turn red.

In contrast, the CSS you add to the shadow DOM is local to the hosting element and does not affect other elements in the DOM:

<div><p>Light DOM</p></div>
<div id="host"></div>
 
<script>
  const elem = document.querySelector('#host');
  const shadowRoot = elem.attachShadow({mode: 'open'});
  shadowRoot.innerHTML = `
    <p>Shadow DOM</p>
    <style>p {color: red}</style>`;
 
</script>

You can also place style rules in an external style sheet, like this:

shadowRoot.innerHTML = `
  <p>Shadow DOM</p>
  <link rel="stylesheet" href="style.css">`;

To get a reference to the element to which the shadowRoot is attached, use the host attribute:

<div id="host"></div>
 
<script>
  const elem = document.querySelector('#host');
  const shadowRoot = elem.attachShadow({mode: 'open'});
 
  console.log(shadowRoot.host);    // => <div id="host"></div>
</script>

To do the opposite and get a reference to the element’s managed shadow root, use the element’s shadowRoot attribute:

<div id="host"></div>
 
<script>
  const elem = document.querySelector('#host');
  const shadowRoot = elem.attachShadow({mode: 'open'});
 
  console.log(elem.shadowRoot);    // => #shadow-root (open)
</script>

shadowRoot mod

When you call the Element.attachShadow() method to attach the shadow root, you must specify the wrapping mode of the shadow DOM tree by passing an object as an argument, otherwise a TypeError will be thrown. The object must have a mode property with a value of open or closed.

Shadow root enabled allows you to access shadow root elements from outside the root using the shadowRoot attribute of the host element, as shown in the following example:

<div><p>Light DOM</p></div>
<div id="host"></div>
 
<script>
  const elem = document.querySelector('#host');
 
  // attach an open shadow root to #host
  const shadowRoot = elem.attachShadow({mode: 'open'});
 
  shadowRoot.innerHTML = `<p>Shadow DOM</p>`;
  // Nodes of an open shadow DOM are accessible
  // from outside the shadow root
  elem.shadowRoot.querySelector('p').innerText = 'Changed from outside the shadow root';
  elem.shadowRoot.querySelector('p').style.color = 'red';
</script>

However, if the mode attribute has a value of “closed”, an attempt to access elements of shadow root from outside of root using JavaScript throws a TypeError:

<div><p>Light DOM</p></div>
<div id="host"></div>
 
<script>
  const elem = document.querySelector('#host');
 
  // attach a closed shadow root to #host
  const shadowRoot = elem.attachShadow({mode: 'closed'});
 
  shadowRoot.innerHTML = `<p>Shadow DOM</p>`;
 
  elem.shadowRoot.querySelector('p').innerText = 'Now nodes cannot be accessed from outside';
  // => TypeError: Cannot read property 'querySelector' of null 
</script>

When mode is set to CLOSED, the shadowRoot property returns null. Because a null value does not have any properties or methods, calling querySelector() on it results in a TypeError. Browsers often use Shadow Roo with it turned off to make the implementation of certain elements internally inaccessible and impossible to change from JavaScript.

To determine whether the shadow DOM is in open or closed mode, you can refer to the mode property of the shadow root:

<div id="host"></div>
 
<script>
  const elem = document.querySelector('#host');
  const shadowRoot = elem.attachShadow({mode: 'closed'});
 
  console.log(shadowRoot.mode);    // => closed
</script>

On the surface, the enclosing shadow DOM looks very convenient for Web component authors who do not want to expose their component’s shadow root, but in practice it is not difficult to bypass the enclosing shadow DOM. Often it takes more work to completely hide the shadow DOM than it’s worth.

Not all HTML elements can host a shadow DOM

Only a limited set of elements can host the shadow DOM. The following table lists the supported elements:

+----------------+----------------+----------------+
|    article     |      aside     |   blockquote   |
+----------------+----------------+----------------+
|     body       |       div      |     footer     |
+----------------+----------------+----------------+
|      h1        |       h2       |       h3       |
+----------------+----------------+----------------+
|      h4        |       h5       |       h6       |
+----------------+----------------+----------------+
|    header      |      main      |      nav       |
+----------------+----------------+----------------+
|      p         |     section    |      span      |
+----------------+----------------+----------------+

Attempts to attach a shadow DOM tree to other elements will result in a “DomException” error. Such as:

document.createElement('img').attachShadow({mode: 'open'});    
// => DOMException

It is not reasonable to use the element as the shadow host, so it is not surprising that this code throws an error. Another reason you might get a DomException error is because the browser already hosts the shadow DOM with this element.

The browser automatically appends the shadow DOM to certain elements

Shadow DOM has been around for a long time, and browsers have used it to hide the internal structure of elements such as ,

When you use the

To display the shadow root of such an element in Chrome, open the Chrome DevTools Settings (press F1) and select “Show User Agent Shadow DOM” under the Elements section:

When the “Show User Agent shadow DOM” option is selected, the shadow root node and its children will become visible. Here’s how the same code looks with this option enabled:

Host the shadow DOM on the custom element

Custom Elements created by the Custom Elements API can host the shadow DOM just like any other element. Take a look at the following example:

<my-element></my-element>
<script>
  class MyElement extends HTMLElement {
    constructor() {
 
      // must be called before the this keyword
      super();
 
      // attach a shadow root to <my-element>
      const shadowRoot = this.attachShadow({mode: 'open'});
 
      shadowRoot.innerHTML = `
        <style>p {color: red}</style>
        <p>Hello</p>`;
    }
  }
 
  // register a custom element on the page
  customElements.define('my-element', MyElement);
</script>

This code creates a custom element that hosts the shadow DOM. It calls the customElements. Define () method with the element name as the first argument and the class object as the second argument. This class extends HtmlElement and defines the behavior of the element.

In the constructor, super() is used to set up the prototype chain and attach the Shadow root to the custom element. When you use

on a page, it creates its own shadow DOM:

Remember that a valid custom element cannot be a single word and must contain a hyphen (-) in the name. For example, myElement cannot be used as the name of a custom element and will throw a DomException error.

Style the host element

In general, to style the host element, you need to add CSS to the light DOM, because that’s where the host element is. But what if you need to style the host element in the shadow DOM?

This is where the host() pseudo-class function comes in. This selector allows you to access the shadow host from anywhere in the shadow root. Here’s an example:

<div id="host"></div>
 
<script>
  const elem = document.querySelector('#host');
  const shadowRoot = elem.attachShadow({mode: 'open'});
 
  shadowRoot.innerHTML = `
    <p>Shadow DOM</p>
    <style>
      :host {
        display: inline-block;
        border: solid 3px #ccc;
        padding: 0 15px;
      }
    </style>`;
</script>

It is worth noting that the host is only available in the shadow root. Also keep in mind that style rules defined outside shadow root are more specific than those defined in :host.

For example, #host {font-size: 16px; } has a higher priority than shadow DOM :host {font-size: 20px; }. This is actually useful as it allows you to define default styles for your component and allow the component’s users to override your styles. The only exception is! Important rule, which is special in the shadow DOM.

You can also pass a selector as an argument to :host(), which allows you to locate the host only if it matches the specified selector. In other words, it allows you to locate different states of the same host:

<style> :host(:focus) { /* style host only if it has received focus */ } :host(.blue) { /* style host only if has a blue  class */ } :host([disabled]) { /* style host only if it's disabled */ } </style>

Context-based styles

To select a shadow root host inside a particular ancestor, you can use the :host-context() pseudo-class function. Such as:

:host-context(.main) {
  font-weight: bold;
}

This CSS code selects shadow host only if it is a descendant of.main:

<body class="main">
  <div id="host">
  </div>
</body>

:host-context() is particularly useful for topics because it allows authors to style components based on the context they use.

Style hook

One interesting aspect of Shadow DOM is its ability to create “style placeholders” and allow the user to fill them in. This can be done by using CSS custom properties. Let’s look at a simple example:

<div id="host"></div> <style> #host {--size: 20px; } </style> <script> const elem = document.querySelector('#host'); const shadowRoot = elem.attachShadow({mode: 'open'}); shadowRoot.innerHTML = ` <p>Shadow DOM</p> <style>p {font-size: var(--size, 16px); }</style>`; </script>

The shadow DOM allows the user to override the font size of his paragraph. Set the value using a custom property representation (-size: 20px), and the shadow DOM retrieves the value using the var() function (font-size: var(-size, 16px)). Conceptually, this is similar to how the

element works.

Inheritable styles

The shadow DOM allows you to create individual DOM elements without seeing selector visibility from the outside, but this does not mean that inherited properties cannot pass through the shadow boundary.

Some properties (such as color, background, and font-family) pass a shadow boundary and apply it to the shadow tree. Therefore, shadow DOM is not a very strong barrier compared to iFrame.

<style>
  div {
    font-size: 25px;
    text-transform: uppercase;
    color: red;
  }
</style>
 
<div><p>Light DOM</p></div>
<div id="host"></div>
 
<script>
  const elem = document.querySelector('#host');
  const shadowRoot = elem.attachShadow({mode: 'open'});
 
  shadowRoot.innerHTML = `<p>Shadow DOM</p>`;
</script>

The solution is simple: reset the inheritable style to its initial value by declaring all: initial, as shown below:

<style>
  div {
    font-size: 25px;
    text-transform: uppercase;
    color: red;
  }
</style>
 
<div><p>Light DOM</p></div>
<div id="host"></div>
 
<script>
  const elem = document.querySelector('#host');
  const shadowRoot = elem.attachShadow({mode: 'open'});
 
  shadowRoot.innerHTML = `
    <p>Shadow DOM</p>
    <style>
      :host p {
        all: initial;
      }
    </style>`;
</script>

In this case, the element is forced back to its original state, so styles that cross the shadow boundary do not work.

Relocation event

Events fired within the shadow DOM can pass through the shadow boundary and bubble into the light DOM; However, the value of event.target is automatically changed so that it looks as if the Event originated from the shadow tree it contains rather than the host element of the actual element.

This change is called event redirection, and the reason behind it is to preserve the shadow DOM encapsulation. Please refer to the following example:

<div id="host"></div>
 
<script>
  const elem = document.querySelector('#host');
  const shadowRoot = elem.attachShadow({mode: 'open'});
 
  shadowRoot.innerHTML = `
    <ul>
      <li>One</li>
      <li>Two</li>
      <li>Three</li>
    <ul>
    `;
 
  document.addEventListener('click', (event) => {
    console.log(event.target);
  }, false);
</script>

When you click anywhere in the shadow DOM, this code will return

records to the console, so the listener cannot see the actual element scheduling the event.

But there is no retargeting in the shadow DOM, and you can easily find the actual element associated with the event:

<div id="host"></div>
 
<script>
  const elem = document.querySelector('#host');
  const shadowRoot = elem.attachShadow({mode: 'open'});
 
  shadowRoot.innerHTML = `
    <ul>
      <li>One</li>
      <li>Two</li>
      <li>Three</li>
    </ul>`;
   
  shadowRoot.querySelector('ul').addEventListener('click', (event) => {
    console.log(event.target);
  }, false);  
</script>

Note that not all events are propagated from the shadow DOM. Those do reposition, but others are simply ignored. If you are using custom events, you need to use the composed:true flag, otherwise events will not pop out of the shadow boundary.

Shadow DOM V0 and V1

The original version of the Shadow DOM specification was implemented in Chrome 25 as Shadow DOM V0 at the time. The new version of the specification improves many aspects of the Shadow DOM API.

For example, an element can no longer host more than one shadow DOM, and some elements cannot host a shadow DOM at all. Violation of these rules can lead to mistakes.

In addition, Shadow DOM V1 provides a new set of capabilities, such as turning on Shadow mode, backing up content, and so on. You can find the specification where v0 written in one of the authors and comprehensive comparison between v1 (https://hayato.io/2016/shadow)… . A complete description of Shadow DOM V1 can be found at the W3C.

Browser support for Shadow DOM v1

At the time of this writing, Shadow DOM V1 is fully supported in Firefox and Chrome. Unfortunately, Edge does not yet implement V1, and Safari only partially supports it. In Can I use… An up-to-date list of supported browsers is provided on.

To implement Shadow DOM on a browser that does not support Shadow DOM v1, use shadydom and shadyCSS polyfills.

conclusion

The lack of encapsulation has always been an issue in DOM development. The Shadow DOM API provides an elegant solution to this problem by providing us with the ability to demarcate the DOM.

Now, style conflicts are no longer a concern, and selectors don’t get out of hand. Shadow DOM is a game-changer for widget development, and the ability to create widgets that are encapsulated from the rest of the page and not affected by other stylesheets and scripts is a huge advantage.

As mentioned earlier, Web components are made up of three major technologies, and Shadow DOM is a key part of that. Hopefully, after reading this article, it will be easier for you to understand how these three technologies work together to build Web components.


This article first send WeChat messages public number: front-end pioneer

Welcome to scan the two-dimensional code to pay attention to the public number, every day to push you fresh front-end technology articles


Read on for the other great articles in this column:

  • 12 Amazing CSS Experiment Projects
  • 50 React Interview Questions You Must Know
  • What are the front-end interview questions at the world’s top companies
  • 11 of the best JavaScript dynamic effects libraries
  • CSS Flexbox Visualization Manual
  • React from a designer’s point of view
  • The holidays are boring? Write a little brain game in JavaScript!
  • How does CSS sticky positioning work
  • A step-by-step guide to implementing animations using HTML5 SVG
  • Programmer 30 years old before the monthly salary is less than 30K, which way to go
  • 14 of the best JavaScript data visualization libraries
  • 8 top VS Code extensions for the front end
  • A complete guide to Node.js multithreading
  • Convert HTML to PDF 4 solutions and implementation