preface

In the process of relearning JavaScript, I learned about Web components, and some of them felt very similar to some of the concepts in Vuejs, such as Web components:

  • HTML templates, the template tag
  • Custom elements
  • Shadow DOM and slot tags
  • Shadow DOM implements style isolation

Let’s take a look at how these Web components are similar to some of the concepts in VUE.

Web Components

What exactly is a Web component?

Web components are a set of tools for enhancing DOM behavior, including shadow DOM, custom elements, and HTML templates.

Currently, Web Components are not widely used due to the following problems:

  • There is no universal “Web Components” specification
  • The Web component has a backward incompatible versioning problem
  • Browser implementations are extremely inconsistent

Because of these problems, using Web components often requires the introduction of a Web component library that simulates missing Web components in the browser. For example, Polymer and LitElement are recommended for the new project.

HTML template – The template tag

We can start by thinking about the following problems and try to give some solutions.

Question: how to set the corresponding three background colors as red, green and blue respectivelypTag, 3s after dynamic rendering in the specified location, such as<div id="root"></div>

Plan 1: Use innerHtml

    let divRoot =  document.querySelector("#root");
    setTimeout(() = >{
        divRoot.innerHTML = ` 

Make me red!

Make me blue!

Make me green!

`
},3000); Copy the code

Solution 2: Use createElement + createDocumentFragment + appendChild

    let divRoot =  document.querySelector("#root");
    let colors = ['red'.'blue'.'green'];
    
    let fragment =  document.createDocumentFragment();
    for (const color of colors) {
      const p = document.createElement('p');
      p.className = color;
      p.innerText = `Make me ${color}! `
      fragment.appendChild(p);
    }
    setTimeout(() = >{
        divRoot.appendChild(fragment);
     },3000);
Copy the code

Both plan 1 and Plan 2 can achieve the corresponding effect, but they are not friendly to writing HTML structure. Firstly, they can not do the friendly prompt like writing HTML directly. Secondly, they are only applicable to the content with very simple structure, once the structure content has multiple layers of nesting, Simply designing HTML structures becomes complicated.

use<template>The label

PS: <template>Tags are native to HTML, not unique to VUE, so don’t get confused.

Before Web components, there was a lack of a mechanism to build a DOM subtree based on HTML parsing and then render that subtree as needed.

In Web components, you can pre-write special tags in the page by using the

<body>
  <template id="tpl">
    <p>I'm inside a custom element template!</p>
  </template>
</body>
Copy the code

When you check the content of a web page with developer tools in the browser, you can see that the node content rendered in the

Here is a solution to the above problem with the

<body>

  <div id="root"></div>

  <template id="tpl">
    <p class="red">Make me red!</p>
    <p class="blue">Make me blue!</p>
    <p class="green">Make me green!</p>
  </template>

  <script>
    let divRoot = document.querySelector("#root");
    let tpl = document.querySelector("#tpl").content;
    setTimeout(() = > {
      divRoot.appendChild(tpl);
    }, 3000);
  </script>
</body>
Copy the code

Template Template script

If the corresponding JS script exists in the Template tag, the execution of the script can be delayed until the contents of the DocumentFragment are actually added to the DOM tree, that is, the execution of the JS script can be delayed.

Look directly at the following example:

<body>
    <div id="foo"></div>
    <template id="bar">
     <script>
         console.log('Template script executed');
     </script>
    </template>

    <script>
    const fooElement = document.querySelector('#foo');
    const barTemplate = document.querySelector('#bar');
    const barFragment = barTemplate.content;

    console.log('About to add template');// 1. About to add template
    fooElement.appendChild(barFragment);//2. Template script executed
    console.log('Added template');// 3. Added template
    </script>
</body>
Copy the code

Shadow DOM — Shadow DOM

Take a look at the following questions first, and then consider:

How do you render different styles for many similar structures in HTML?

Typically, to apply a unique style to each subtree without using the style attribute, you need to add a unique class name to each subtree and then style them with the appropriate selector.

Existing problems:

  • A unique style selector must be used to determine which style to render
  • The styles are all applied to the top-level DOM tree, even if the current presentation requires very few styles
  • Without true CSS style isolation, it is easy to cause style conflicts due to writing problems

Ideally, you should be able to restrict CSS to the DOM that uses them.

What is a shadow DOM?

Using the shadow DOM, you can add an entire DOM tree as a node to the parent DOM tree.

DOM encapsulation can be implemented, which means that CSS styles and CSS selectors can be limited to shadow DOM subtrees rather than the entire top-level DOM tree.

Create shadow DOM

The shadow DOM is created and added to a valid HTML element using the attachShadow() method:

  • Shadow host is the element that holds the shadow DOM
  • Shadow root is the root node of the shadow DOM
  • The attachShadow() method requires a shadowRootInit object, that is, this object must contain a mode attribute with a value of “open” or “closed”
  • References to the shadow DOM with the mode attribute of “open” are available on HTML elements using the shadowRoot attribute, whereas references to the shadow DOM with the attribute of “closed” are not
document.body.innerHTML = ` 
      
`
; const foo = document.querySelector('#foo'); const bar = document.querySelector('#bar'); // Create shadow nodes for different DOM elements. Each DOM node can have only one shadow DOM const openShadowDOM = foo.attachShadow({ mode: 'open' }); const closedShadowDOM = bar.attachShadow({ mode: 'closed' }); // Access the shadow root node directly console.log(openShadowDOM); // #shadow-root (open) console.log(closedShadowDOM); // #shadow-root (closed) // Add content and styles to the shadow DOM, where styles are completely isolated and no style conflicts occur openShadowDOM.innerHTML = `

this is red

`
closedShadowDOM.innerHTML = `

this is blue

`
// Access the shadow root node through the shadow host console.log(foo.shadowRoot); // #shadow-root (open) console.log(bar.shadowRoot); // null Copy the code

Synthetic slot with shadow DOM slot slot

The shadow DOM is designed for custom Web components that require support for nested DOM fragments, meaning that HTML that resides in the shadow host needs a mechanism to render into the shadow DOM, but does not need to reside in the shadow DOM tree.

[Shadow DOM has highest priority]

Normally, as soon as the shadow DOM is added to an element, the browser gives it the highest priority and gives priority to rendering its content over the original DOM content, as in the following example:

document.body.innerHTML = ` 
      

I'm foo's child

`
; const foo = document.querySelector('#foo'); const openShadowDOM = foo.attachShadow({ mode: 'open' }); // Add content to the shadow DOM openShadowDOM.innerHTML = `

this is openShadowDOM content

`
Copy the code

[ <slot>Tag]

To display the original HTML content in the shadow host, we need to use the

tag to tell the browser where to place the original HTML content. Change the above example to the following form:

document.body.innerHTML = ` 
      

I'm foo's child

`
; const foo = document.querySelector('#foo'); const openShadowDOM = foo.attachShadow({ mode: 'open' }); // Add content to the shadow DOM openShadowDOM.innerHTML = `

this is openShadowDOM content

`
Copy the code

[Named slot]

By matching slot/name pairs, such as in the example below, elements with slot=”foo” are projected onto

with name=”foo”.

    document.body.innerHTML = ` 
      

Foo

Bar

`
; let shadowDom = document.querySelector('div').attachShadow({ mode: 'open' }); shadowDom.innerHTML = ` `; Copy the code

Event redirection in the shadow DOM

If a browser event (such as click) occurs in the shadow DOM, the browser needs a way for the parent DOM to handle the event.

Note: Event redirection only happens to elements that actually exist in the shadow DOM. Elements that are projected in from outside the shadow DOM using the

tag do not get event redirection because technically they still exist outside the shadow DOM.

// Create an element as a shadow host
document.body.innerHTML = ` 
      
`
; // Add a shadow DOM and insert HTML into it document.querySelector('div') .attachShadow({ mode: 'open' }) .innerHTML = ` `; // When the button is clicked: // Handled inside: // Handled outside:
Copy the code

Restrictions on creating shadow DOM

For security reasons and to avoid shadow DOM collisions, not all elements can contain a shadow DOM, and trying to add a shadow DOM to an invalid element or an element that already has a shadow DOM will result in a throw error.

Here are the elements that can hold the shadow DOM:

< Any custom element created with a valid name ><article>
<aside>
<blockquote>
<body>
<div>
<footer>
<h1>
<h2>
<h3>
<h4>
<h5>
<h6>
<header>
<main>
<nav>
<p>
<section>
<span>
Copy the code

Custom elements

Custom elements introduce an object-oriented programming style to HTML elements, in which custom, complex, and reusable elements can be created, and instances can be created using simple HTML tags or attributes.

Create custom elements

Browsers try to incorporate unrecognized elements into the DOM as generic elements, as shown in the following example.

Custom elements take this one step further and allow you to define complex behavior for the

tag as it appears, as well as incorporate it into element lifecycle management in the DOM.

document.body.innerHTML = ` 
      
       I'm inside a custom element.
       `;
console.log(document.querySelector('x-box') instanceof HTMLElement); // true
Copy the code

customElements.define()

CustomElements are created by calling the customElements.define() method. The power of customElements lies in the class definition.

class BoxElement extends HTMLElement {
    constructor() {
        super(a);console.log('x-box')
    }
}

customElements.define('x-box', BoxElement);
document.body.innerHTML = ` 
       
       
       `;

// x-box
// x-box
// x-box
Copy the code

Is and extents

If a custom element inherits an element class, you can use the IS attribute and extends option to specify the label as an instance of that custom element.

class BoxElement extends HTMLDivElement {
    constructor() {
        super(a);console.log('x-box')
    }
}

customElements.define('x-box', BoxElement, { extends: 'div' });
document.body.innerHTML = ` 
      
`
; // x-box // x-box // x-box Copy the code

Add custom element content

class BoxElement extends HTMLElement {
  constructor() {
    super(a);/ / way
     this.innerHTML = `
      
this is content1!
`
; 2 / / way let div = document.createElement('div'); div.innerText = `this is content2! `; this.appendChild(div); 3 / / way this.attachShadow({ mode: 'open' }); this.shadowRoot.innerHTML = `

I'm inside a custom element!

`
; } } customElements.define('x-box', BoxElement); document.body.innerHTML += `<x-box></x-box`; Copy the code

Use custom element lifecycle methods

Custom elements have the following five lifecycle methods:

  • Constructor () : Called when an element instance is created or when an existing DOM element is upgraded to a custom element
  • ConnectedCallback () : Called each time this custom element instance is added to the DOM
  • DisconnectedCallback () : Called each time the custom element instance is removed from the DOM
  • AttributeChangedCallback () : Called every time the value of an observable attribute changes, and the definition of the initial value changes once the element instance is initialized
  • AdoptedCallback () : Moves the custom element instance to the new document via document.adoptNode()
class BoxElement extends HTMLElement {
  constructor() {
    super(a);console.log('ctor');
  }
  connectedCallback() {
    console.log('connected');
  }
  disconnectedCallback() {
    console.log('disconnected');
  }
}
customElements.define('x-box', BoxElement);
const BoxElement = document.createElement('x-box');

// ctor 
document.body.appendChild(BoxElement);
// connected 
document.body.removeChild(BoxElement);
// disconnected
Copy the code

Upgrade custom elements

It is not always possible to define a custom element and then use the corresponding element tag in the DOM. There are methods on The CustomElementRegistry that a Web component can use to check if a custom element is defined and then use to upgrade an existing element.

  • If the custom elements have been defined, then CustomElementRegistry. The get () method returns the corresponding class of custom elements
  • CustomElementRegistry. WhenDefined () method returns a futures, when after the corresponding custom element is defined to solve
customElements.whenDefined('x-box').then(() = > console.log('defined! '));

console.log(customElements.get('x-box')); // undefined 

customElements.define('x-box'.class {});// defined! 

console.log(customElements.get('x-box'));// class BoxElement {}
Copy the code

Connected to the DOM elements in a custom element is defined automatically upgrade, if you want to forced to upgrade before connection to the DOM element, you can use CustomElementRegistry. Upgrade () method.

// The HTMLUnknownElement object is created before the custom element is defined
const boxElement = document.createElement('x-box');

// Create a custom element
class BoxElement extends HTMLElement {}
customElements.define('x-box', BoxElement);
console.log(boxElement instanceof BoxElement); // false

// Force an upgrade
customElements.upgrade(boxElement);
console.log(boxElement instanceof BoxElement); // true
Copy the code

conclusion

By now, you should be able to see if something in the Web component is similar to something in the VUE, for example:

vuejs

  • <template>The label
  • <slot>slot
  • <style scope>Local style
  • <component is="name">Dynamic components

Web components

  • templateThe template
  • In the shadow DOM<slot>slot
  • In the shadow DOM<style>Isolation of style
  • In a custom componentisProperties andextendsoptions

Note: of course similarity is similarity, its essence is still different, so do not confuse, but can be regarded as some reference in concept, so it is not difficult to find that a lot of knowledge content is actually the same.