头图

cause

Speaking of this thing, it has been out for at least 2 years, but because I basically haven't done anything on the web side in the past two years, so I haven't solved it much. Taking advantage of this holiday, I will add some knowledge points.

Let's take a look at the description of him in MDN:

Web Components aims to solve these problems — it consists of three main technologies that can be used together to create custom elements that encapsulate functionality, and can be reused anywhere you like without worrying about code conflicts.

  • Custom elements: A set of JavaScript APIs that allow you to define custom elements and their behavior, which can then be used as needed in your user interface.
  • Shadow DOM : A set of JavaScript APIs for attaching an encapsulated "shadow" DOM tree to an element (rendered separately from the main document DOM) and controlling its associated functionality. This way you can keep the functionality of your elements private so they can be scripted and styled without worrying about conflicting with the rest of the document.
  • HTML templates: <template> and <slot> elements allow you to write markup templates that are not displayed in the rendered page. They can then be reused multiple times as the basis for custom element structures.

Custom elements

A very important feature of the Web Components standard is that it enables developers to encapsulate the functionality of an HTML page as custom elements (custom tags), whereas in the past, developers had to write a lot of lengthy, deeply nested tags to achieve Same page functionality.

CustomElementRegistry

Used to handle custom elements in web documents — this object allows you to register a custom element, returns information about registered custom elements, and so on.

There are two kinds of custom elements:

  • Autonomous custom elements are independent elements that do not inherit from other built-in HTML elements. You can directly write them in the form of HTML tags to use on the page. For example <popup-info> , or document.createElement("popup-info") like this.
  • Customized built-in elements inherit from basic HTML elements. When creating, you must specify the element to be extended (as shown in the above example). When using, you need to write the basic element tag first, and specify the name of the custom element through the is attribute. For example <p is="word-count"> , or document.createElement("p", { is: "word-count" }) .

customElements.define

Used to register a custom element, the method accepts the following parameters

  • A string representing the DOMString standard that represents the name of the element being created. Note that the name of the custom element cannot be a single word and must have dashes in it.
  • Class used to define element behavior.
  • 可选参数 , a configuration object containing the extends property, is an optional parameter. It specifies which built-in element the created element inherits from, and can inherit from any built-in element.

Small chestnuts:

 <div is="my-name"></div>
<gender-info></gender-info>
<script>
    // 基础 HTML 的元素
    class Names extends HTMLDivElement {
        constructor() {
            super();
            // this 指向的就是当前元素
            this.innerText = 'Grewer wrote example'
        }
    }

    customElements.define('my-name', Names, {extends: 'div'});


    // 自定义标签:
    class Gender extends HTMLElement {
        constructor() {
            // 必须首先调用 super方法
            super();

            this.innerText = 'Grewer gender is male'

            // 添加方法
            this.onclick = () => {
                console.log('run in customElement')
            }
        }
    }

    customElements.define('gender-info', Gender)
</script>

Display of the page:

Display in DOM:

Note on component names

  • There must be "-" If the component name is gender, an error will be reported directly, even such a name can be "gender-"
  • The first letter cannot be capitalized, for example, it cannot be written as Gender-info

The life cycle

In the constructor of a custom element, you can specify several different callback functions, which will be called at different times of the element's lifetime:

  • connectedCallback : Called when the custom element is first inserted into the document DOM.
  • disconnectedCallback : Called when a custom element is removed from the document DOM.
  • adoptedCallback : Called when the custom element is moved to a new document.
  • attributeChangedCallback : Called when custom element adds, deletes, or modifies its own attributes.

Most of the lifecycles are similar to other frameworks, but one of them attributeChangedCallback needs to be explained:

 <life-test></life-test>

<script>

    class Life extends HTMLElement {
        // 用来搭配 attributeChangedCallback, 控制要监听的具体属性
        static get observedAttributes() {
            return ['style', 'test'];
        }

        constructor() {
            super();
            this.innerText = 'life test  click'
            this.onclick = this.change
        }

        change = () => {
            console.log('add run')
            this.style.background = `rgba(0, 0, 0, ${Math.random()})`
            this.setAttribute('test', Math.random() * 100)
        }
        
        attributeChangedCallback(...arg) {
            // 打印的值分别是: 属性值名, 旧值, 新值  如果没有就为 null
            // 如果同时改变了 2 个属性, 则触发此函数两次
            console.log('changed', arg)
        }
    }

    customElements.define('life-test', Life)
    
</script>

If you want to use the attributeChangedCallback life cycle, you must match it with observedAttributes
View the above dom online: click to view

shadow DOM

An important property of web components is encapsulation - markup structure, style and behavior can be hidden and isolated from other code on the page, ensuring that different parts are not mixed together, making the code more clean and tidy. Among them, the Shadow DOM interface is the key, it can attach a hidden, independent DOM to an element.

After the definition of the component, of course, the control of scoped is indispensable, and shadow DOM is used to do this

Shadow DOM allows to attach a hidden DOM tree to a regular DOM tree - it starts with the shadow root node, and below this root node, can be any element, just like a normal DOM element.

Here, there are some Shadow DOM-specific terms we need to know:

  • Shadow host: A regular DOM node to which the Shadow DOM will be attached.
  • Shadow tree: The DOM tree inside the Shadow DOM.
  • Shadow boundary: Where the Shadow DOM ends, and where the regular DOM begins.
  • Shadow root: The root node of the Shadow tree.

Basic use:

 <div id="foo"></div>
<body>
<script>
    const div = document.getElementById('foo')
    // 将普通 dom 转换为 shadow dom
    let shadow = div.attachShadow({mode: 'open'});


    // 获取 shadow dom 对象
    // 如果 mode: 'open' 则能够正常获取, 如果为 closed  则 shadowObj 是 null
    const shadowObj = div.shadowRoot
    console.log(shadowObj)


    const p = document.createElement('p')
    p.textContent = 'this is p'
    // 关于 textContent 和 innerText 的区别: https://developer.mozilla.org/zh-CN/docs/Web/API/Node/textContent#%E4%B8%8E_innertext_%E7%9A%84%E5%8C%BA%E5%88%AB

    shadow.append(p)
</script>

What I feel lacks is that it can only be converted into shadow dom after the dom is created, not in the browser creation stage

add styles

 <style>
    p{
        background-color: #444444;
    }
</style>
<div id="foo"></div>
<p>不在 shadow 中的 p</p>
<body>
<script>
    //省略前一步的代码

    const style = document.createElement('style');

    style.textContent =  `
        p {
            background-color: #2c9edd;
        }
    `
    shadow.append(style)

    // 通过运行可以看到 p 的样式有了一个 scoped
</script>

View online: click to view

Of course we can also use external styles:

 // 将外部引用的样式添加到 Shadow DOM 上
const linkElem = document.createElement('link');
linkElem.setAttribute('rel', 'stylesheet');
linkElem.setAttribute('href', 'style.css');

// 将所创建的元素添加到 Shadow DOM 上
shadow.appendChild(linkElem);

Use Shadow Dom with Custom Element

 <life-test></life-test>
<script>
  class Life extends HTMLElement {

    constructor() {
      super();
      const shadow = this.attachShadow({mode: 'closed'});
        
      const p = document.createElement('p')
      p.textContent = 'click me'
      shadow.append(p)
      this.p = p

      this.onclick = this.change
    }

    change = () => {
      console.log('add run')
      this.p.style.background = `rgba(0, 0, 0, ${Math.random()})`
      this.p.setAttribute('test', Math.random() * 100)
    }

    connectedCallback() {
      console.log('connectedCallback', '初始化')
    }

  }

  customElements.define('life-test', Life)
</script>

View online: click to view

templates and slots

templates

As the name suggests, it is a template, the most basic use:

Under normal circumstances, the template will not be displayed on the web page

 <template id="my-paragraph">
    <p>My paragraph</p>
</template>
<p>2 秒钟后执行插入</p>
</body>
<script>
    function show() {
        let template = document.getElementById('my-paragraph');
        let templateContent = template.content;
        document.body.appendChild(templateContent);
    }
    // 这里我们就是要拿到模板中的内容 插入到网页
    // 就像框架中的组件的使用
    setTimeout(show, 2000)
</script>

slot

Browser support for this feature is less than <template>, supported in Chrome 53, Opera 40, Safari 10, Firefox 59 and Edge 79

The combined use of shadow dom and template, slot

 <template id="my-paragraph">
    <p>
        下面会显示插槽的内容:
        <slot name="my-text"></slot>
    </p>
</template>
<div id="container">
    <span slot="my-text">Let's have some different text!</span>
</div>
<script>
    let template = document.getElementById('my-paragraph');
    let templateContent = template.content;

    const shadowRoot = document.getElementById('container').attachShadow({mode: 'open'})
        .appendChild(templateContent.cloneNode(true));
</script>

View online: click to view

The slot here is basically similar to the one in the Vue framework

Full WebComponents use:

Let's go a step further and add Custom Element to form a complete Web Components

 <template id="my-paragraph">
    <p>
        下面会显示插槽的内容:
        <slot name="my-text"></slot>
    </p>
</template>
<my-paragraph>
    <span slot="my-text">Let's have some different text!</span>
</my-paragraph>
<script>
    customElements.define('my-paragraph',
        class extends HTMLElement {
            constructor() {
                super();
                let template = document.getElementById('my-paragraph');
                let templateContent = template.content;

                const shadowRoot = this.attachShadow({mode: 'open'})
                    .appendChild(templateContent.cloneNode(true));
            }
        })
</script>

View online: click to view

It's easier to understand by looking at the dom display in Chrome:

Compatible and polyfill

Speaking of this, we have to talk about compatibility issues. Through caniuse, we can check his compatibility level:



It can be seen that, except for the slot, other compatibility has reached more than 94%, of course, unfortunately, there is no ie11

The slot compatibility is slightly less, but also 92%:

polyfill

If the above compatibility is not perfect, we also have polyfills to help you:

This is the polyfill repository for webComponents: https://github.com/webcomponents/polyfills

After using the polyfill, the compatibility level now looks like this:

Polyfill Edge IE11+ Chrome* Firefox* Safari 9+* Chrome Android* Mobile Safari*
Custom Elements
Shady CSS/DOM

* Indicates the current version of the browser

This polyfill may work in older browsers, but requires the use of other polyfills (such as classList or polyfills for other platforms). We cannot guarantee support for browsers outside the compatibility list.

Now that polyfill is added, its compatibility is more secure

Framework expansion

The following lists several frameworks based on WebComponents, if you are interested, you can take a look:

Epilogue

webComponents shows a comprehensive set of native support component solutions. However, because of the development of the framework, it is more troublesome to work hard on native support. For our developers, there are now 3 options.

  1. Start from 0, use or build a foundation based on native WebComponents
  2. Pay attention to big frameworks (such as Vue/React), the tendency to use this API and the process
  3. Try some of the WebComponents based frameworks I showed above

However, after reading some comments on the Internet, there are still some pain points.

When the author wrote this article, there was an idea - WebComponents should also be a micro-front-end implementation

This topic can continue to be extended, if you are interested, I will talk about it later

refer to:


Grewer
984 声望28 粉丝

Developer