What is shadow DOM

<video controls autoplay name="media">
  <source id="mp4" src="trailer.mp4" type="video/mp4">
</video>Copy the code

This is the simplest video TAB, with default buttons like volume. There is no trace at all in the source code. So where do these nodes come from? This is called shadow DOM, and the visual control looks like this in the browser:

Shadow DOM browser

Note that #shadow-root is grayed out. This is the browser’s way of indicating that content representing other parts of the page in the Shadow DOM does not affect the interior (it can be accessed in a specific way, as discussed later).

Content specifically refers to CSS selectors and javascript code

In short, Shadow DOM is a new specification for HTML that allows developers to encapsulate HTML components (similar to VUE components, which extract separate HTML, CSS, and JS parts).

Why shadow DOM

The name of Bootstrap is familiar to you, and the code is generally as follows:

<ul class="media-list">
  <li class="media">
    <div class="media-left">
       <a href="#">! [] (...). </a> </div> <div class="media-body">
       <h4 class="media-heading">Media heading</h4>
    </div>
  </li>
</ul>Copy the code

It is very simple and easy to use, but you do not have a thorough understanding of these things, often the structure becomes complex, are a pile of templates, modification is a difficult problem, affecting the whole body. The advantage of shadow DOM in this case is that you can write templates like this

<bootstrap-media-list> <a href="#">! [] (...). </a> <h4 class="media-heading">Media heading</h4>
</bootstrap-media-list>Copy the code

Of course, you need some JS and CSS to do this

How to use

Let’s run an example

<div class="widget">Hello, world! </div> <script> var host = document.querySelector('.widget');
  var root = host.createShadowRoot();
  root.textContent = 'I'm in your div! ';
</script>Copy the code

The results



The host node
shadow host
shadow root

How do I render the content in the host node

Rendering only the content in the shadow root is rarely useful, but having the freedom to render the content in the host node makes the page more flexible. We need the content tag

<div class="pokemon"> </div> <template class="pokemon-template">< h1> A wild <content></content> appeared! </h1> </template> <script> var host = document.querySelector('.pokemon');
  var root = host.createShadowRoot();
  var template = document.querySelector('.pokemon-template');
  root.appendChild(document.importNode(template.content, true)); 
</script>Copy the code



<content>
The insertion point
.pokemon
select

<div class="host""> <p> Zhuge Liang evolved from great kindness and compassion. </p> <span class="name"> </span> </div> <template class="root-template">< dl> <dt> Name </dt> <dd><content select=".name"></content></dd>
    </dl>
    <p><content select=""></content></p>
</template>
<script>
    var host = document.querySelector('.host');
    var root = host.createShadowRoot();
    var template = document.querySelector('.root-template');
    root.appendChild(template.content);
</script>Copy the code



select



<content select=""></content>


  • <content></content>
  • <conent select=""></conent>
  • <content select="*"></content>

Style rendering and wrapping

Let’s start with a simple example

<style>
    button {
        font-size: 18px;
        font-family: 'Chinese Script'; } </style> <button> <div></div> <script> var host = document.querySelector('div');
    var root = host.createShadowRoot();
    root.innerHTML = 
      '<style>button { font-size: 24px; color: blue; } </style>'+
      ;
</script>Copy the code



shadow DOM
The embodiment of scoping

(:host) selector

The :host is a pseudo-class selector that selects the host node, so we can extend the example above

<style> p { font-size: 12px; } </style> <p> my text </p> <button> my button </button> <template class="shadow-template">
    <style>
        :host(p) {
            color: green;
        }
        :host(button) {
            color: red;
        }
        :host(*) {
            font-size: 24px;
        }
    </style>
    <content select=""></content>
</template>
<script>
    var root1 = document.querySelector('p').createShadowRoot();
    var root2 = document.querySelector('button').createShadowRoot();

    var template = document.querySelector('.shadow-template');

    root1.appendChild(document.importNode(template.content, true));
    root2.appendChild(document.importNode(template.content, true));
</script>Copy the code


  • P tag font size is 12px = shadow styles have less priority than page styles
  • :hostAny valid selector can be used in the selector,*Apply to all
  • Theming can be achieved by mounting different hosts to render different content

    The above theme is not complete, and the selection is based only on mount elements, that is.parent > .childBut we can still pass:host-contextimplementation.parent < .childThe following
    <div class="serious">
      <p class="serious-widget">
          serious-widget
      </p>
    </div>
    <div class="playful">
      <p class="playful-widget">
          playful-widget
      </p>
    </div>
    <template class="widget-template">
      <style>
          :host-context(.serious) {
              width: 250px;
              height: 50px;
              background: tomato;
          }
          :host-context(.playful) {
              width: 250px;
              height: 50px;
              background: deepskyblue;
          }
      </style>
      <content></content>
    </template>
    <script>
    var root1 = document.querySelector('.serious-widget').createShadowRoot();
    var root2 = document.querySelector('.playful-widget').createShadowRoot();
    var template = document.querySelector('.widget-template');
    root1.appendChild(document.importNode(template.content, true));
    root2.appendChild(document.importNode(template.content, true));
    </script>Copy the code


The above results are pretty good for dynamic component building

Ps: pseudo class, pseudo element selector can also be used directly, the effect and normal node in the same

(:: Content) selector

Keep content and presentation separate when using shadow DOM, meaning that text should come from the page and not be buried in the Shadow DOM template. So we need to render the distributed nodes in the template.

<div class="widget"< p style = "max-width: 100%; clear: both; min-height: 1em; </button> </div> <template class="widget-template">
    <style>
        ::content > button {
            color: white;
            background: tomato;
            border-radius: 10px;
            border: none;
            padding: 10px;
        }
    </style>
    <content select=""></content>
</template>
<script>
var root = document.querySelector('.widget').createShadowRoot();
var template = document.querySelector('.widget-template');
root.appendChild(document.importNode(template.content, true));
</script>Copy the code

Break scope (::shadow)

We can use ::shadow in the mount node, for example

<style>
    .sign-up::shadow #username{
        font-size: 20px;
        border: 1px solid red;
    }
</style>
<div class="sign-up"></div>
<template class="sign-up-template">
    <style>
        #username{
            font-size: 12px;
        }
    </style>
    <div>
        <input type="text" id="username" placeholder="Username">
    </div>
</template>
<script>
var root = document.querySelector('.sign-up').createShadowRoot();
var template = document.querySelector('.sign-up-template');
root.appendChild(document.importNode(template.content, true));
</script>Copy the code


The downside is we can only penetrate one floor, but we still have one artifact!

Multilayer penetration (/deep/)

<style>
    #foo /deep/ button {
        color: red;
    }
</style>
<div id="foo"></div>
<template>
    <div id="bar"></div>
</template>
<script>
    var root1 = document.querySelector('#foo').createShadowRoot();
    var template = document.querySelector('template');
    root1.appendChild(document.importNode(template.content, true));
    var root2 = root1.querySelector('#bar').createShadowRoot();
    root2.innerHTML = '';
</script>Copy the code


The difference between the javascript

  1. Data is not block-level and is still mounted inwindowon
  • Event redirection (Events that were originally bound to the Shadow DOM node are redirected, so they look like they are bound to the host node)
    <input id="normal-text" type="text" value="I'm normal text">
    <div id="host"></div>
    <template>
      <input id="shadow-text" type="text" value="I'm shadow text">
    </template>
    <script>
      var root = document.querySelector('#host').createShadowRoot();
      var template = document.querySelector('template');
      root.appendChild(document.importNode(template.content, true));
      document.addEventListener('click'.function(e) {
        console.log(e.target.id + ' clicked! ');
      });
    </script>Copy the code

You can see that events at the shadow node are brokered by the host node.

Blocking events

Blocked at the root of the shadow node when listening for:

  • aborterror
  • select
  • change
  • load
  • reset
  • reset
  • resize
  • scroll
  • selectstar
    <input id="normal-text" type="text" value="I'm normal text">
    <div id="host">
      <input id="distributed-text" type="text" value="I'm distributed text">
    </div>
    <template>
      <div><content></content></div>
      <div>
          <input id="shadow-text" type="text" value="I'm shadow text">
      </div>
    </template>
    <script>
      var root = document.querySelector('#host').createShadowRoot();
      var template = document.querySelector('template');
      root.appendChild(document.importNode(template.content, true));
      document.addEventListener('select'.function(e) {
        console.log(e.target.id + ' text selected! ');
      });
    </script>Copy the code

The root of the event shadow node is prevented from bubbling up to the Ducoment, so it cannot listen.

Distribution of nodes

Distribution nodes are those that previously projected the content of the host node with the

tag. Distribution nodes do not block as above because this is just a projection of the actual content that is still mounted on the host node.