The current front-end development is basically dependent on the React and Vue frameworks, and many custom component libraries are derived from these two frameworks:
- Element (Vue)
- Ant Design (React)
The emergence of these component libraries allows us to directly use the components that have already been packaged, and with the help of the open source community, there are many template projects (Vue-element-Admin, Ant Design Pro) that allow us to quickly start a project.
Although React and Vue provide convenience for our component development, in terms of component development ideas, one is the self-created JSX syntax and the other is the unique single-file template syntax, both of which aim to provide a component encapsulation method. After all, there are original things in it, and we just started to contact the Web foundation of HTML, CSS, JS way or some discrepancy. Today’s introduction is, through THE HTML, CSS, JS way to achieve custom Components, is also the current browser native solution: Web Components.
What are Web Components?
Web Components itself is not a single specification, but rather consists of a set of DOM apis and HTML specifications for creating reusable HTML tags with custom names that can be used directly in your Web application.
Code reuse has always been our goal. In JS, reusable code can be encapsulated into a function, but for complex HTML (including related styles and interactive logic), we have no good way to reuse. Web Components are designed to complement the browser’s ability to use either a back-end templating engine or a secondary encapsulation of the DOM API using existing frameworks.
How do I use Web Components?
Web Components contains several specifications, all of which have been normalized in the W3C and HTML standards, and consists of three main parts:
- Custom Elements: A set of JavaScript apis for creating Custom HTML tags and allowing actions to be taken when the tags are created or destroyed;
- Shadow DOM: a set of JavaScript apis for inserting a created DOM Tree into an existing element. The DOM Tree cannot be modified externally, and the element is not affected elsewhere.
- Templates for HTMLThrough:
<template>
,<slot>
The template is written directly in an HTML file and then obtained through the DOM API.
Custom Elements
The browser provides a method, customElements.define(), to define a custom tag. This method takes three arguments:
- The name of the custom element, a DOMString standard string, must be a name with a dash to prevent conflicts of custom elements (
e.g. custom-tag
). - Define some behavior of custom elements, similar to the lifecycle in React and Vue.
- Extended parameter (optional). The parameter type is an object and must contain
extends
Property to specify which built-in element the created element inherits from (e.g. { extends: 'p' }
).
Here are some examples to demonstrate its usage. The complete code is put on the JS Bin.
Create a new HTML tag
Let’s start by looking at how to create a new custom element.
class HelloUser extends HTMLElement {
constructor() {
// The super method must be called
super(a);// Create a div tag
const $box = document.createElement("p");
let userName = "User Name";
if (this.hasAttribute("name")) {
// Read the value of the name attribute, if it exists
userName = this.getAttribute("name");
}
// Set the text content of the div tag
$box.innerText = `Hello ${userName}`;
// Create a shadow node to which the other elements should be attached
const shadow = this.attachShadow({ mode: "open"}); shadow.appendChild($box); }}// Define an element named
customElements.define("hello-user", HelloUser);
Copy the code
<hello-user name="Shenfq"></hello-user>
Copy the code
A
tag is generated on the page with the text Hello Shenfq. This form of custom elements, called Autonomous Custom Elements, is a stand-alone element that can be used directly in HTML.
Extend existing HTML tags
In addition to defining a new HTML tag, we can also extend an existing HTML tag. For example, if we want to encapsulate a component with similar capabilities to the
-
tag, we can do this:
class SkillList extends HTMLUListElement {
constructor() {
// The super method must be called
super(a);if (
this.hasAttribute("skills") &&
this.getAttribute("skills").includes(', ')) {// Read the value of the skills attribute
const skills = this.getAttribute("skills").split(', ');
skills.forEach(skill= > {
const item = document.createElement("li");
item.innerText = skill;
this.appendChild(item); })}}}// Extend the
tag
customElements.define("skill-list", SkillList, { extends: "ul" });
Copy the code
<ul is="skill-list" skills="js,css,html"></ul>
Copy the code
To extend an existing tag, you need to use the third parameter of the customElements. Define method, and the second parameter class must inherit the corresponding class that needs to extend the tag. To use it, you only need to add the is attribute to the tag. The value of the attribute is the name defined in the first parameter.
The life cycle
The life cycle of the custom element is relatively simple, with only four callbacks:
connectedCallback
: called when a custom element is inserted into the PAGE’s DOM document.disconnectedCallback
: called when a custom element is removed from the DOM document.adoptedCallback
: called when a custom element is moved.attributeChangedCallback
: called when a custom element is added, deleted, or modified.
Here’s how to use it:
class HelloUser extends HTMLElement {
constructor() {
// The super method must be called
super(a);// Create a div tag
const $box = document.createElement("p");
let userName = "User Name";
if (this.hasAttribute("name")) {
// Read the value of the name attribute, if it exists
userName = this.getAttribute("name");
}
// Set the text content of the div tag
$box.innerText = `Hello ${userName}`;
// Create a shadow node to which the other elements should be attached
const shadow = this.attachShadow({ mode: "open" });
shadow.appendChild($box);
}
connectedCallback() {
console.log('Create element')
// Move the element to iframe after 5s
setTimeout(() = > {
const iframe = document.getElementsByTagName("iframe") [0]
iframe.contentWindow.document.adoptNode(this)},5e3)}disconnectedCallback() {
console.log('Delete element')}adoptedCallback() {
console.log('Move element')}}Copy the code
<! -- Insert an iframe into the page to move the custom element -->
<iframe width="0" height="0"></iframe>
<hello-user name="Shenfq"></hello-user>
Copy the code
After the element is created, wait for 5 seconds, and then move the custom element into the iframe document. At this time, you can see the console log for deleting and moving the element simultaneously.
Shadow DOM
We used the Shadow DOM earlier when we introduced custom elements. The function of Shadow DOM is to isolate the internal elements from the external elements, so that the structure, style, and behavior of custom elements are not affected by the external elements.
We can see that the
tag defined earlier, inside Elements in the console, will display a shadow-root, indicating that there is a shadow DOM inside.
Before Web Components were introduced, browsers used Shadow DOM to encapsulate some internal elements, such as
Then in Elements on the console, you can see that there is also a shadow-root inside the
Create Shadow DOM
We can create a Shadow DOM inside any of the nodes, and after fetching the Element, call the element.attachshadow () method to attach a new shadow-root to the Element.
This method accepts an object and has only one mode attribute, which is open or closed, indicating whether the nodes in the Shadow DOM can be obtained externally.
<div id="root"></div>
<script>
// Get the page
const $root = document.getElementById('root');
const $p = document.createElement('p');
$p.innerText = 'Create a shadow node';
const shadow = $root.attachShadow({mode: 'open'});
shadow.appendChild($p);
</script>
Copy the code
Mode of the differences
As mentioned earlier, if the mode value is open or closed, the main difference is whether you can use Element. ShadowRoot to get shadow-root for some operations.
<div id="root"></div>
<script>
// Get the page
const $root = document.getElementById('root');
const $p = document.createElement('p');
$p.innerText = 'Create a shadow node';
const shadow = $root.attachShadow({mode: 'open'});
shadow.appendChild($p);
console.log('is open', $div.shadowRoot);
</script>
Copy the code
<div id="root"></div>
<script>
// Get the page
const $root = document.getElementById('root');
const $p = document.createElement('p');
$p.innerText = 'Create a shadow node';
const shadow = $root.attachShadow({mode: 'closed'});
shadow.appendChild($p);
console.log('is closed', $div.shadowRoot);
</script>
Copy the code
Templates for HTML
In the previous case, there was an obvious flaw in using the DOM API to manipulate the DOM, which was significantly less efficient than Vue’s templates and React’s JSX. To solve this problem, the
and
tags were introduced into the HTML specification.
Use the template
A template is simply an ordinary HTML tag that can be thought of as a div, except that the contents of the element are not displayed on the interface.
<template id="helloUserTpl">
<p class="name">Name</p>
<a target="blank" class="blog"># #</a>
</template>
Copy the code
In JS, we can directly obtain the instance of the template through the DOM API. After obtaining the instance, it is generally not possible to directly modify the elements in the template. Instead, we need to call tpl.content.cloneNode to make a copy, because the template on the page is not a one-off.
// Get the tag by ID
const tplElem = document.getElementById('helloUserTpl');
const content = tplElem.content.cloneNode(true);
Copy the code
After we get the copy of the template, we can perform some operations on the template and insert it into the Shadow DOM.
<hello-user name="Shenfq" blog="http://blog.shenfq.com" />
<script>
class HelloUser extends HTMLElement {
constructor() {
// The super method must be called
super(a);// Get the tag by ID
const tplElem = document.getElementById('helloUserTpl');
const content = tplElem.content.cloneNode(true);
if (this.hasAttribute('name')) {
const $name = content.querySelector('.name');
$name.innerText = this.getAttribute('name');
}
if (this.hasAttribute('blog')) {
const $blog = content.querySelector('.blog');
$blog.innerText = this.getAttribute('blog');
$blog.setAttribute('href'.this.getAttribute('blog'));
}
// Create a shadow node to which the other elements should be attached
const shadow = this.attachShadow({ mode: "closed"}); shadow.appendChild(content); }}// Define an element named
customElements.define("hello-user", HelloUser);
</script>
Copy the code
Add the style
The
tag can be inserted directly into the
<template id="helloUserTpl">
<style>
:host {
display: flex;
flex-direction: column;
width: 200px;
padding: 20px;
background-color: #D4D4D4;
border-radius: 3px;
}
.name {
font-size: 20px;
font-weight: 600;
line-height: 1;
margin: 0;
margin-bottom: 5px;
}
.email {
font-size: 12px;
line-height: 1;
margin: 0;
margin-bottom: 15px;
}
</style>
<p class="name">User Name</p>
<a target="blank" class="blog"># #</a>
</template>
Copy the code
The host pseudo-class is used to define the shadow-root style, which is the style of the tag that wraps the template.
Placeholder element
A placeholder element is one that occupies a position in a template and then specifies what to display at that position when the element is inserted into the interface.
<template id="helloUserTpl">
<p class="name">User Name</p>
<a target="blank" class="blog"># #</a>
<! -- placeholder -->
<slot name="desc"></slot>
</template>
<hello-user name="Shenfq" blog="http://blog.shenfq.com">
<p slot="desc">Welcome to the public account: More awesome front end</p>
</hello-user>
Copy the code
The usage here is consistent with the usage of Vue slot, so I will not introduce it too much.
conclusion
That’s enough to cover the basic use of Web Components. Using Web Components has the following advantages over other frameworks that support componentization solutions:
- Browser native support, no need to introduce additional third-party libraries;
- Real internal private CSS, no style conflicts;
- A componentization scheme that can be implemented without compilation and is isolated from the external DOM;
The main drawback of Web Components is that the standards may not be stable yet. For example, the modularization scheme for templates, which was not mentioned in this article, has been abolished and there is no formal scheme to introduce template files. The native API works, but it’s not easy to use, or there wouldn’t be a library like jQuery to manipulate the DOM. Fortunately, there are a number of frameworks that are implemented based on Web Components, and we will focus on the framework lit-HTML and Lit-Element that use Web Components in the beginning of this article.
Ok, today’s article here, I hope you can harvest.