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. 页面显示
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")
- 说明:Autonomous custom elements 是独立的元素,可以直接在body内使用,例如
-
Customized built-in elements
- 说明:Customized built-in elements 继承自基本的HTML元素。在创建时,必须指定继承的元素,也就是
{ extends: 'div' }
,在body中使用<ul is="task-list">
,js创建document.createElement('ul',{ is:'task-list' })
- 说明:Customized built-in elements 继承自基本的HTML元素。在创建时,必须指定继承的元素,也就是
7. 定义Autonomous custom elements组件
- 使用shadow root外部样式影响不到组件
<div>
<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);
页面显示
- 不使用shadow root 外部样式可以影响组件
<div>
<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);
页面展示
8. 定义Customized built-in elements组件
<div>
<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' });
页面展示
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));
}
}
);
页面展示
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));
}
}
);
页面展示
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>