Take you through -> Shadow DOM & Browser Native Component Development (Web Components API)

This paper introduces

We are used to using frameworks like Vue and React to develop components, but we can develop components natively without relying on any framework, so one of the great advantages of this native API is that we can develop components natively without relying on any framework.

Browser support for components is a big trend, but it’s not good enough to use at the moment, but that doesn’t stop us from learning and curiosity, and I believe that native components will become a mainstream way of writing components in a few years. Let’s learn about it now.

1. The compatibility

Chrome 54 Safari 10.1 Firefox 63

MDN display:

Direct access to the production environment is not recommended.

2. Shadow elements

I still remember the first time I used Qiankun.js framework to see this concept (the following article will write about the micro front end of the actual practice), this technology can achieve a part of the CSS style isolation, the reason said only to achieve a part of the style isolation, after learning this article you will understand.

Step 1: Generate the shadow element

Let’s create a new HTML5 page with the following structure

<! DOCTYPE html> <html lang="en"> <head> <style> #cc-shadow { margin: auto; border: 1px solid #ccc; width: 200px; height: 200px; } </style> </head> <body> <div id="cc-shadow"> <span> </div> <script> const oShadow =" document.getElementById("cc-shadow"); const shadow = oShadow.attachShadow({mode: 'open'}); </script> </body> </html>

A strange scene occurs where the internal elements are not visible and a special structure definition appears in the console where the structure is viewed.

  1. attachShadowMethod mounts one for the specified elementShadow DOM.
  2. mode: openRepresents that the Shadow DOM can be retrieved from a JavaScript method within the page.
  3. mode: openIn view of the isdom.shadowRootMethod getElementsByClassName directly or can be obtained (This is a very important one. Some of the articles are wrong).
  4. withmode: openThe corresponding ismode: close.
  5. Note: it is not possible to open and then close this operation
Step 2: Inject elements into it
const link = document.createElement("a"); link.href = 'xxxxxxxxxxxx'; Link.innerHTML = 'Click me to jump '; shadow`.appendChild(link);
  1. Notice that this is usedshadowRather thandomItself.
Step 3: Inject styles into it
 const styles = document.createElement("style");
 styles.textContent = `* { color:red  } `
 shadow.appendChild(styles);
  1. As you can see from above, one is createdstyleThe label is inserted.
  2. And similarly we can create oneThe link tagAnd the same thing happens when you insert it.

The effect is as follows:

Step 4: Experiment with style isolation
 styles.textContent = `
       * { color:red  } 
       body {
         background-color: red;
       }
    `

Here we’ve changed it inside the shadow elementbodyThis style does not apply to the outsidebodyHis body.

Step 5: Pattern penetration experiment

Do you feel like the sandbox is perfect for isolating CSS? Let’s now add a font size style inside the outermost style tag, because shadow elements cannot isolate inheritable styles.

* {
    font-size: 40px;
  }

The effect is as follows:

To summarize:

The shadow element does prevent the style from leaking out and contaminating the whole world, but it does not prevent the style from contaminating the whole world. In this case, the inheritable style. For example, if you use an id to get the element inside the shadow and change the border attribute, it is not valid. So Qiankun. js is currently unable to perfectly isolate styles. For example, if you want to change the global style, you need to rely on js for help. With that in mind, let’s move on to the next major native component.

The complete code (to copy and play):

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, Margin-top: 0px; margin-bottom: 0px; margin-bottom: 0px; margin-bottom: 0px; margin-bottom: 0px; margin-bottom: 0px; margin-bottom: 0px; margin-bottom: 0px; border: 1px solid #ccc; width: 200px; height: 200px; } * { font-size: 40px; } < / style > < / head > < body > < div id = "cc - shadow" > < span > I am internal elements < / span > < / div > < script > / / 1: Const oShadow = document.getElementById("cc-shadow"); const shadow = oShadow.attachShadow({ mode: 'open' }); // 2: Injecting element const link = document.createElement("a"); link.href = 'xxxxxxxxxxxx'; Link.innerHTML = 'Click me to jump '; shadow.appendChild(link); // 3: const styles = document.createElement("style"); styles.textContent = ` * { color:red } body { background-color: red; } '// 4: Insert to use. Insert CSS in the same way you would insert link, shadow. AppendChild (styles); </script> </body> </html>

3. Use of native components

Here’s one I madeThe native components, and the use method is attached.

<cc-mw name=" image1 "> <cc-mw name=" image2" > / imgs/slim. Webp "/ > < / cc - mw > < cc - mw name =" archenemy 2 image = "".. /imgs/ limlo.webp "></cc-mw>

The use of the above component looks similar to the components in a framework like Vue, but it is very delicate!

Matters needing attention
  1. The name of the custom element must contain a conjunction line to distinguish it from the native HTML element. So,<cc-mw>Can not write<ccMw>
  2. If you write it this way with the closing tag removed, only the first component will be displayed. The second component will not be rendered (this is really weird). The second component will be inserted into the first component by default and will not be displayed because it is inserted into the shadow element.

    <cc-mw name=" image1 "> <cc-mw name=" image2" > /imgs/ limloo.webp "/> <cc-mw name=" image2 "="... /imgs/ limlu.webp "/>

Weird phenomena are not the subject of this time, we continue to study dry goods.

4. Write the component firsttemplate

The DOM structure inside the template is equivalent to the structure inside the shadow element.

  <template id="ccmw">
    <style>
      :host {
        border: 1px solid red;
        width: 200px;
        margin-bottom: 10px;
        display: block;
        overflow: hidden;
      }

      .image {
        width: 70px;
        height: 70px;
      }

      .container {
        border: 1px solid blue;
      }

      .container>.name {
        font-size: 20px;
        margin-bottom: 5px;
      }
    </style>

    <img class="image">
    <div class="container">
      <p class="name"></p>
    </div>
  </template>

Explain the knowledge points one by one:

The first:domdefine

Here we can define the DOM as normal, just write it the same way it was written outside.

Second: Define IDs

The third:<style>The label

We can think of the inside of the template tag as the inside of a shadow element’s structure, so we can insert the style tag here without using JS assistance.

Fourth::host

Select the Shadow host that contains the Shadow DOM that uses this CSS, which is the component’s shell parent.

5. The component class

Writing a component of course requires logical code, and JS is here to stay.

  <script>
    class CcMw extends HTMLElement {
      constructor() {
        super();
        var shadow = this.attachShadow({ mode: 'closed' });
        var templateElem = document.getElementById('ccmw');
        var content = templateElem.content.cloneNode(true);
        content.querySelector('img').setAttribute('src', this.getAttribute('image'));
        content.querySelector('.container>.name').innerText = this.getAttribute('name');
        shadow.appendChild(content);
      }
    }
    window.customElements.define('cc-mw', CcMw);
  </script>

Explain the knowledge points one by one:

The first:HTMLElement

The interceptionw3schoolFrom the above definition, we know that this superclass is given to the componentdomThe underlying attribute of the element.

Number two: old friendsattachShadow

Make the DOM a shadow container so that components can stand on their own.

The third:templateElem.content.cloneNode(true)

Clones the elements in the template. Clones because the components will be reused.

Fourth:window.customElements.define('cc-mw', CcMw);

The component name is bound to the class name. Officially, this object can be used to register new custom elements and get information about previously registered custom elements.

Fifth: Get the external elements inside the component

You can retrieve large external elements from within a component, so you can operate globally. Use caution.

It is even possible to insert the component directly into the body. Note that this is allowed, but not encouraged.

Sixth:thisWho is it

    thisIt’s the element itself.

By the endShadow elementsIs it very easy to understand the operation above are doing, open not happy.

Attached is the complete code for everyone to play with:

<! DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title> native Component </title> <style> /* does not affect internal style */. Name {border: 2px solid red; } < / style > < / head > < body > < cc - mw name = "archenemy 1 image =" ".. / imgs/slim. Webp "/ > < / cc - mw > < cc - mw name =" archenemy 2 image = "".. /imgs/ limlo.webp "></cc-mw> <template id=" CCMW ">< style> :host {display: block; overflow: hidden; border: 1px solid red; width: 200px; margin-bottom: 10px; } .image { width: 70px; height: 70px; } .container { border: 1px solid blue; } .container>.name { font-size: 20px; margin-bottom: 5px; } </style> <img class="image"> <div class="container"> <p class="name"></p> </div> </template> <script> class CcMw extends HTMLElement { constructor() { super(); var shadow = this.attachShadow({ mode: 'closed' }); var templateElem = document.getElementById('ccmw'); var content = templateElem.content.cloneNode(true); content.querySelector('img').setAttribute('src', this.getAttribute('image')); content.querySelector('.container>.name').innerText = this.getAttribute('name'); shadow.appendChild(content); } } window.customElements.define('cc-mw', CcMw); </script> </body> </html>

6. Integration into a JS file

The problem with the above code is how the component code and the business code are put together. Of course, we can use a trick to break them apart. Here we use the template string JS to generate the template dynamic insertion.

<! DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title> native Component </title> <style> /* does not affect internal style */. Name {border: 2px solid red; } < / style > < / head > < body > < cc - mw name = "archenemy 1 image =" ".. / imgs/slim. Webp "/ > < / cc - mw > < cc - mw name =" archenemy 2 image = "".. /imgs/ limloo.webp "></cc-mw> <script SRC ="./2. Js "></script> </body> </ HTML >

The above code is much cleaner, now we can concentrate on writing a plug-in:./2. A decoupling. Js

const template = document.createElement('template');
 
template.innerHTML = `
  <style>
      :host {
        border: 2px solid red;
        width: 200px;
        margin-bottom: 10px;
        display: block;
        overflow: hidden;
      }

      .image {
        width: 70px;
        height: 70px;
      }

      .container {
        border: 1px solid blue;
      }

      .container>.name {
        font-size: 20px;
        margin-bottom: 5px;
      }
    </style>

    <img class="image">
    <div class="container">
      <p class="name"></p>
    </div>
`

class CcMw extends HTMLElement {
  constructor() {
    super();
    var shadow = this.attachShadow({ mode: 'closed' });
    var content = template.content.cloneNode(true);
    content.querySelector('img').setAttribute('src', this.getAttribute('image'));
    content.querySelector('.container>.name').innerText = this.getAttribute('name');
    shadow.appendChild(content);
  }
}
window.customElements.define('cc-mw', CcMw);

7. Modify data dynamically

How can we call a component if we can’t modify the data? Here we’re going to use class methods.

forComponent classesAdd method:
class UserCard extends HTMLElement { constructor() { // ... this.oName = content.querySelector('.container>.name'); / /... shadow.appendChild(content); } // Add method dynamic changeName(name){this.oname.innerText = name}}

We use the following code on the page where the component is used :(Note: the ID is added for the first component)

<cc > name=" mw" id="mw" image="... / imgs/slim. Webp "/ > < / cc - mw > < cc - mw name =" archenemy 2 image = "".. /imgs/ limloo.webp "></cc-mw> <script SRC ="./2. Js "></script> <script> const mw = document.getElementById('mw'); SetTimeout (()= bb0 {mW.changeName (' Modify the Demon '); }, 1000) </script>

Other modifications are the same.

8. slotslot

In the template code, add: (if not, the default copy will be displayed)

< div class = "container" > < p class = "name" > < / p > < slot name = "MSG" > default copy < / slot > < / div >

When used:

<cc > name=" mw" id="mw" image="... / imgs/slim. Webp "/ > < span slot =" MSG "> evolved < / span > < / cc - mw > < cc - mw name =" archenemy 2 image = "".. /imgs/ limlo.webp "></cc-mw>

The effect is as follows:

end.

This technology may not be necessary for the time being, but learning this knowledge can enable us to have a broader vision of technology, continuous learning will always be useful, this is the case, I hope to make progress with you.