Web-Components 定义,使用以及示例

1. 定义

web-componets 类似于vue以及react中组件定制,将一组ui以及公共逻辑抽取,并且封装成为一个公共组件。在页面可以随处调用

2. 实现web component的基本方法

a. 创建一个类或函数来指定web组件的功能

class Dialog extends HTMLElement {
	constructor() {
		super();
		const shadow = this.attachShadow({ mode: 'open' });
		const p = document.createElement('p');
		const text = this.getAttribute('dialog-text');
		p.textContent = text;
		shadow.appendChild(p);
	}
}

b. 用 customElements.define() 方法注册自定义的元素 ,并且指定component名称,以及创建的类。

customElements.define('dialog-element', Dialog);

c. 在页面中使用

<body>
	<dialog-element dialog-text="this is dialog-text"></dialog-element>
</body>

d. 页面显示
image

4. 生命周期

  • connectedCallback
    • 当 custom element首次被插入文档DOM时,被调用
  • disconnectedCallback
    • 当 custom element从文档DOM中删除时,被调用
  • adoptedCallback
    • 当 custom element被移动到新的文档时,被调用
  • attributeChangedCallback
    • 当 custom element增加、删除、修改自身属性时,被调用
  • 生命周期使用实例
class Dialog extends HTMLElement {
    constructor() {
        super();
    }
    static get observedAttributes() {
        return ['dialog-text'];
    }

    connectedCallback() {
        console.log('dialog element added to page.');
    }

    disconnectedCallback() {
        console.log('dialog element removed from page.');
    }

    adoptedCallback() {
        console.log('dialog element moved to new page.');
    }

    attributeChangedCallback(name, oldValue, newValue) {
        console.log('dialog element attributes changed.');
    }
}

5. window.customElements属性

  • customElements.define
    • 定义:定义一个自定义元素
    • 语法:customElements.define(name, constructor, options);
    • 参数:
      • name:自定义元素名,所创建的元素名称的符合 DOMString 标准的字符串。注意,custom element 的名称不能是单个单词,且其中必须要有短横线
      • constructor: 自定义元素的构造类
      • options:控制元素如何定义 . 目前有一个选项支持
        • extends. 指定继承的已创建的元素. 被用于创建自定义元素.

示例

// customElements.define('dialog-element', Dialog);
// customElements.define('dialog-element', Dialog, { extends: 'div' });
// customElements.define('dialog-element', class extends HTMLElement {});
  • customElements.get
    • 定义:返回以前定义自定义元素的构造函数,如果获取不到,则返回undefined
    • 语法:const constructor = customElements.get(name);
    • 参数:
      • name: 自定义元素的名字

示例

const constructor = customElements.get('dialog-element');
console.log(constructor, `constructor`);
// class Dialog {}
  • customElements.whenDefined
    • 定义:当一个元素被定义时,返回一个promise,当获取到自定义元素时,立即resolve,当元素名称不是一个有效名称时,则立即执行rejecte
    • 语法:customElements.whenDefined(name)
    • 参数:
      • name: 自定义元素的名字
class Dialog extends HTMLElement {
    constructor() {
        super();

        const shadow = this.attachShadow({ mode: 'open' });

        const p = document.createElement('p');
        p.textContent = 'this is dialog element';

        shadow.appendChild(p);
    }
}
customElements.define('dialog-element', Dialog);

customElements.whenDefined('dialog-element').then(() => {
    console.log(`dialog-element is defined`);
});

customElements
    .whenDefined('aaa')
    .then(() => {
        console.log(`dialog-element is defined`);
    })
    .catch(err => {
        console.log(err, `err`);
// Failed to execute 'whenDefined' on 'CustomElementRegistry': "aaa" is not a valid custom element name
    });

6. custom elements类别

  • Autonomous custom elements
    • 说明:Autonomous custom elements 是独立的元素,可以直接在body内使用,例如<task-list></task-list>,或者js创建document.createElement("task-list")
  • Customized built-in elements
    • 说明:Customized built-in elements 继承自基本的HTML元素。在创建时,必须指定继承的元素,也就是{ extends: 'div' },在body中使用<ul is="task-list">,js创建document.createElement('ul',{ is:'task-list' })

7. 定义Autonomous custom elements组件

  • 使用shadow root外部样式影响不到组件
<div style="width: 300px; height: 300px; border: 1px solid red">
    <task-list data-list="[1,2,3,4,5,6,7,8]"></task-list>
</div>
class TaskList extends HTMLElement {
    constructor() {
        super();
        // 这里的this就是task-list元素

        // 创建一个shadow root
        const shadow = this.attachShadow({ mode: 'open' });

        // 通过getAttribute方法获取标签上的属性
        const dataList = JSON.parse(this.getAttribute('data-list') || '[]');

        // 创建一个ul元素
        const ul = document.createElement('ul');
        ul.classList.add('task-list');

        // 循环创建li元素
        dataList.forEach(item => {
            const li = document.createElement('li');
            li.classList.add('task-item');
            li.textContent = item;
            li.addEventListener('click', this.showItemContent.bind(this, li));
            ul.appendChild(li);
        });

        // 创建样式
        const style = document.createElement('style');
        style.textContent = this.defineStyle();

        // 将样式添加至shadow 根节点
        shadow.appendChild(style);

        // 将ul添加至shadow 根节点
        shadow.appendChild(ul);
    }
    /**
     * 定义组件样式,外部style无法影响组件样式
     */
    defineStyle() {
        return `
            .task-list{
                list-style: none;
            }
        `;
    }

    showItemContent(ele) {
        console.log(ele.textContent, `ele.textContent`);
    }
}
// 定义task-list组件
customElements.define('task-list', TaskList);

页面显示
image

  • 不使用shadow root 外部样式可以影响组件
<div style="width: 300px; height: 300px; border: 1px solid red">
    <task-list data-list="[1,2,3,4,5,6,7,8]"></task-list>
</div>
<style>
    .task-list {
        list-style: none;
        background-color: gray;
    }
</style>
class TaskList extends HTMLElement {
    constructor() {
        super();
        // 这里的this就是task-list元素

        // 通过getAttribute方法获取标签上的属性
        const dataList = JSON.parse(this.getAttribute('data-list') || '[]');

        // 创建一个ul元素
        const ul = document.createElement('ul');
        ul.classList.add('task-list');

        // 循环创建li元素
        dataList.forEach(item => {
            const li = document.createElement('li');
            li.classList.add('task-item');
            li.textContent = item;
            li.addEventListener('click', this.showItemContent.bind(this, li));
            ul.appendChild(li);
        });

        // 将ul添加至根节点
        this.appendChild(ul);
    }

    showItemContent(ele) {
        console.log(ele.textContent, `ele.textContent`);
    }
}
// 定义task-list组件
customElements.define('task-list', TaskList);

页面展示
image

8. 定义Customized built-in elements组件

<div style="width: 300px; height: 300px; border: 1px solid red">
    <ul is="task-list" data-list="[1,2,3,4,5,6,7,8]"></ul>
</div>
<style>
    .task-list {
        list-style: none;
        background-color: gray;
    }
</style>
class TaskList extends HTMLUListElement {
    constructor() {
        super();
        // 这里的this就是task-list元素
        this.classList.add('task-list');
        // 通过getAttribute方法获取标签上的属性
        const dataList = JSON.parse(this.getAttribute('data-list') || '[]');

        // 循环创建li元素
        dataList.forEach(item => {
            const li = document.createElement('li');
            li.classList.add('task-item');
            li.textContent = item;
            this.appendChild(li);
        });
    }
}

// 定义task list组件,必须定义{ extends: 'ul' },否则会报错
// Illegal constructor: autonomous custom elements must extend HTMLElement
customElements.define('task-list', TaskList, { extends: 'ul' });

页面展示
image

9. 定义组件的方式

  • template方式
<!-- 这段代码不会在页面展示 -->
<template id="task-list">
    <style>
        .task-list {
            list-style: none;
            background-color: gray;
        }
    </style>
    <ul>
        <li class="item">1</li>
        <li class="item">2</li>
    </ul>
</template>
<!-- 这个组件会在页面展示 -->
<task-list></task-list>
customElements.define(
    'task-list',
    class extends HTMLElement {
        constructor() {
            super();
            let template = document.getElementById('task-list');
            let templateContent = template.content;
            // 使用cloneNode方式将templateContent添加到当前元素中
            this.attachShadow({ mode: 'open' }).appendChild(templateContent.cloneNode(true));
        }
    }
);

页面展示
image

10. 插槽的使用

<!-- 这段代码不会在页面展示 -->
<template id="task-list">
    <style>
        .task-list {
            list-style: none;
            background-color: gray;
        }
    </style>
    <ul>
        <li class="item"><p>1</p></li>
        <li class="item">2</li>
        <!-- 使用name属性定义插槽名称 -->
        <li><slot name="other-item">base item</slot></li>
    </ul>
</template>
<!-- 这个组件会在页面展示 -->
<task-list>
    <!-- 指定插槽 -->
    <p slot="other-item">this is slot content</p>
</task-list>
customElements.define(
    'task-list',
    class extends HTMLElement {
        constructor() {
            super();
            let template = document.getElementById('task-list');
            let templateContent = template.content;
            // 使用cloneNode方式将templateContent添加到当前元素中
            this.attachShadow({ mode: 'open' }).appendChild(templateContent.cloneNode(true));
        }
    }
);

页面展示
image

11. webcomponets示例之notify组件

页面展示:webcomponets示例之notify组件

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>webcomponets示例之notify组件</title>
    </head>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        .notify-container {
            width: 500px;
            box-shadow: 0 0 10px 5px #ccc;
            border-radius: 10px;
            position: fixed;
            left: 0;
            right: 0;
            margin: 0 auto;
            transition: all 0.5s linear;
            transform: translateY(-100px);
        }
        .content-container {
            text-align: center;
            line-height: 30px;
            font-size: 16px;
        }
    </style>
    <body>
        <button onclick="showNotify()">showNotify</button>
        <button onclick="closeNotify()">closeNotify</button>
    </body>
    <script>
        class NotifyElement extends HTMLElement {
            __TIME__ = 3000;
            __TIME__ID__ = null;
            constructor() {
                super();
                this.__TIME__ID__ = null;
            }

            connectedCallback() {
                // 创建notify元素;
                this.notifyContainer = this.notifyContainer || document.createElement('div');
                this.notifyContainer.classList.add('notify-container');

                // 创建内容元素
                this.contentContainer = this.contentContainer || document.createElement('p');
                this.contentContainer.classList.add('content-container');

                this.notifyContainer.appendChild(this.contentContainer);
                this.appendChild(this.notifyContainer);
            }

            /**
             * @description 显示notify
             */
            open(message, time) {
                if (this.__TIME__ID__) {
                    clearTimeout(this.__TIME__ID__);
                    this.__TIME__ID__ = null;
                }
                this.contentContainer.textContent = message;
                setTimeout(() => {
                    this.notifyContainer.style.transform = 'translateY(30px)';
                });

                this.__TIME__ID__ = setTimeout(() => {
                    this.close();
                }, time || this.__TIME__);
            }
            /**
             * @description 关闭notify
             */
            close() {
                this.contentContainer.textContent = '';
                this.notifyContainer.style.transform = 'translateY(-100px)';
                setTimeout(() => {
                    this.remove();
                }, 500);
            }
        }

        if (!customElements.get('notify-element')) {
            customElements.define('notify-element', NotifyElement);
        }

        function showNotify() {
            const notify = document.createElement('notify-element');
            document.body.appendChild(notify);
            notify.open('这是通知消息');
        }
        function closeNotify() {
            notify.close();
        }
    </script>
</html>
posted @ 2022-05-16 14:05  半糖也甜吖  阅读(875)  评论(0编辑  收藏  举报