前言
Web Component: 创建可重用的定制元素
学习内容来源:
调试设置:
谷歌浏览器:设置------preferences------Elements------Show user agent shadow DOM打钩
基本概念
- Custom element(自定义元素):class或者function,定义组件api。
- Shadow DOM(影子 DOM):附加到元素中,可隔离样式。
- HTML template(HTML 模板):
<template>
和<slot>
可以编写不在呈现页面中显示的标记模板。
自定义组件
- 注册组件
js
// TodoItem.js
class TodoItem extends HTMLElement{}
customElements.define("todo-item", TodoItem) // 传入组件名称与组件
html
<!--index.html-->
<!--使用-->
<todo-item></todo-item>
- 创建模板
js
// TodoItem.js
const template=document.createElement("template")
template.innerHTML=`
<style>
label{
display: block;
}
.description{
color: #a9a9a9;
font-size: .8em;
}
</style>
<label>
<input type="checkbox" />
<slot></slot>
<span class="description">
<slot name="description"></slot>
</span>
</label>
`
也可以直接写在html中
html
<!--index.html-->
<template id="myTemp">
<style>
label{
display: block;
}
.description{
color: #a9a9a9;
font-size: .8em;
}
</style>
<label>
<input type="checkbox" />
<slot></slot>
<span class="description">
<slot name="description"></slot>
</span>
</label>
<template>
- 开启shadow dom
js
// TodoItem.js
class TodoItem extends HTMLElement{
constructor(){
super()
// ++ 开启shadow dom
const shadowDom = this.attachShadow({ mode : "open"})
// ++ 如果模板是写在js中,直接克隆模板,并把模板加入到shadow dom中:
shadowDom.appendChild(template.content.cloneNode(true))
// 否则需要获取一下模板:
// let template = ducoment.querySelector('#myTemp')
// shadowDom.appendChild(template.content.cloneNode(true))
}
}
现在可以在插槽中加入一些内容。在模板中设置的<style>
将不会影响到外边的元素:
html
<todo-item>
<!--内容会加到默认slot中 -->
todo1
<!--内容会加到description slot中 -->
<span slot="description">其他描述</span>
</todo-item>
- 生命周期
js
// TodoItem.js
class TodoItem extends HTMLElement{
constructor(){
super()
const shadowDom = this.attachShadow({ mode : "open"})
shadowDom.appendChild(template.content.cloneNode(true))
}
// ++ 自定义元素挂载时被调用
connectedCallback() {}
// ++ 自定义元素卸载时被调用
disconnectedCallback() {}
}
内置元素扩展与组件属性
- 注册组件:继承ul标签
js
// ExpandableList.js
class ExpandableList extends HTMLUListElement {}
customElements.define('expandable-list', ExpandableList, {
extends: 'ul',
});
- 定义组件
js
class ExpandableList extends HTMLUListElement {
constructor() {
super();
this.style.position = 'relative';
// 创建一个子元素
this.toggleBtn = document.createElement('button');
this.toggleBtn.style.position = 'absolute';
this.toggleBtn.style.border = 'none';
this.toggleBtn.style.background = 'none';
this.toggleBtn.style.padding = 0;
this.toggleBtn.style.top = 0;
this.toggleBtn.style.left = '5px';
this.toggleBtn.style.cursor = 'pointer';
this.toggleBtn.innerText = '>';
this.appendChild(this.toggleBtn);
// 定义点击事件
this.toggleBtn.addEventListener('click', () => {
this.dataset.expanded = !this.isExpanded;
});
// 获取某个属性
console.log(this.getAttr('name'))
}
// 获取属性
private getAttr (key) {
return this.getAttribute(key)
}
// 计算属性
get isExpanded() {
return (
this.dataset.expanded !== 'false' && this.dataset.expanded !== null
);
}
// 监听属性
static get observedAttributes() {
return ['data-expanded'];
}
// 监听到上方属性变化后调用
attributeChangedCallback(name, oldValue, newValue) {
this.updateStyles();
}
updateStyles() {
const transform = this.isExpanded ? 'rotate(90deg)' : '';
this.toggleBtn.style.transform = transform;
[...this.children].forEach((child) => {
if (child !== this.toggleBtn) {
child.style.display = this.isExpanded ? '' : 'none';
}
});
}
// 挂载后调用
connectedCallback() {
this.updateStyles();
}
}
customElements.define('expandable-list', ExpandableList, {
extends: 'ul',
});
- 使用:通过is传入扩展
html
<ul is="expandable-list" data-expanded name="myul">
<li>apple</li>
<li>banana</li>
</ul>