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 respectively
p
Tag, 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
tag and have the browser automatically parse them into a DOM subtree, but skip rendering. As follows:
<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
tag is based on the DocumentFragment, which is also an efficient tool for adding elements to THE HTML in batches. At this point, the DocumentFragment is like a minimal Document object corresponding to a subtree. In other words, if you need to operate the node in the
tag, you must first obtain the reference of the corresponding DocumentFragment. Which document. QuerySelector (‘ # TPL ‘). The content.
Here is a solution to the above problem with the
tag:
<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
template
The template- In the shadow DOM
<slot>
slot - In the shadow DOM
<style>
Isolation of style - In a custom component
is
Properties andextends
options
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.