Web components可以将html标签结构、css样式和行为隐藏起来,并从页面上的其他代码中分离开来,这样不同的功能不会混在一起,封装为 custom elements(自定义标签),并且可以运用template重复html结构片段,你用slot配合创建弹性模板
1.custom elements (自主自定义标签----创建一个标签)
基本用法: customElements.define(name, constructor[, options]);
- name --一个DOMString, 用于表示所创建的元素的名称。注意,custom element 的名称中必须要有短横线
- constructor 一个类对象,用于定义元素的行为
- options 可选参数 包含 extends属性的配置对象,指定了所创建的元素继承自哪个内置元素,可以继承任何内置元素
栗子
customElements.define('word-count', WordCount, { extends: 'p' }); // 这个元素叫做 `word-count`,它的类对象是 `WordCount`, 继承自 <p>元素
class WordCount extends HTMLParagraphElement {
constructor() {
// 必须首先调用 super方法
super();
// 元素的功能代码写在这里
...
}
}
共有两种 custom elements:
- Autonomous custom elements 是独立的元素,它不继承其他内建的HTML元素。你可以直接把它们写成HTML标签的形式,来在页面上使用。例如 <popup-info>,或者是document.createElement("popup-info")这样
class PopUpInfo extends HTMLElement {
constructor() {
// 必须首先调用 super方法
super();
// 元素的功能代码写在这里
...
}
}
customElements.define('popup-info', PopUpInfo);
// 页面上
<popup-info>
-
Customized built-in elements 继承自基本的HTML元素。在创建时,你必须指定所需扩展的元素(正如上面例子所示),使用时,需要先写出基本的元素标签,并通过
is
属性指定custom element的名称。例如<p is="word-count">
, 或者document.createElement("p", { is: "word-count" })
class ExpandingList extends HTMLUListElement { // 这里的真正不同点在于元素继承的是HTMLUListElement接口,而不是HTMLElement
constructor() {
// 必须首先调用 super方法
super();
// 元素的功能代码写在这里
...
}
}
customElements.define('expanding-list', ExpandingList, { extends: "ul" });
// 页面上
<ul is="expanding-list">
...
</ul>
参考: https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/Using_custom_elements
2. shadow DOM (Shadow DOM允许将隐藏的DOM树添加到常规的DOM树中)
- 需要了解的 Shadow DOM相关技术:
- Shadow host: 一个常规 DOM节点,Shadow DOM会被添加到这个节点上
- Shadow tree:Shadow DOM内部的DOM树。
- Shadow boundary:Shadow DOM结束的地方,也是常规 DOM开始的地方。
- Shadow root: Shadow tree的根节点。
基本用法: element.attachShadow({mode: 'open' | 'closed'});
- mode 接受open 或者 closed;
- open 表示你可以通过页面内的 JavaScript 方法来获取 Shadow DOM
- 如果你将一个 Shadow root 添加到一个 Custom element 上,并且将 mode设置为closed,那么就不可以在外部获取 Shadow DOM了——myCustomElem.shadowRoot 将会返回 null。浏览器中的某些内置元素就是这样的,例如<video>,包含了不可访问的 Shadow DOM
简单的栗子:
var shadow = this.attachShadow({mode: 'open'});
var wrapper = document.createElement('span');
wrapper.setAttribute('class','wrapper');
shadow.appendChild(wrapper);
复杂点的栗子:(与custom elements配合使用)
// main.js
// Create a class for the element
class Square extends HTMLElement {
// Specify observed attributes so that
// attributeChangedCallback will work
// 触发 attributeChangedCallback()回调函数,必须监听这个属性
// 通过定义observedAttributes() get函数来实现,observedAttributes()函数体内包含一个 return语句,返回一个数组,包含了需要监听的属性名称
static get observedAttributes() {
return ['c', 'l'];
}
constructor() {
// Always call super first in constructor
super();
const shadow = this.attachShadow({mode: 'open'});
const div = document.createElement('div');
const style = document.createElement('style');
shadow.appendChild(style);
shadow.appendChild(div);
}
// 当 custom element首次被插入文档DOM时,被调用。
connectedCallback() {
console.log('Custom square element added to page.');
updateStyle(this);
}
// 当 custom element从文档DOM中删除时,被调用。
disconnectedCallback() {
console.log('Custom square element removed from page.');
}
// 当 custom element被移动到新的文档时,被调用。
adoptedCallback() {
console.log('Custom square element moved to new page.');
}
// 当 custom element增加、删除、修改自身属性时,被调用。
attributeChangedCallback(name, oldValue, newValue) {
console.log('Custom square element attributes changed.');
updateStyle(this);
}
}
customElements.define('custom-square', Square);
function updateStyle(elem) {
const shadow = elem.shadowRoot;
const childNodes = Array.from(shadow.childNodes);
childNodes.forEach(childNode => {
if (childNode.nodeName === 'STYLE') {
childNode.textContent = `
div {
width: ${elem.getAttribute('l')}px;
height: ${elem.getAttribute('l')}px;
background-color: ${elem.getAttribute('c')};
}
`;
}
});
}
const add = document.querySelector('.add');
const update = document.querySelector('.update');
const remove = document.querySelector('.remove');
let square;
update.disabled = true;
remove.disabled = true;
function random(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
add.onclick = function() {
// Create a custom square element
square = document.createElement('custom-square');
square.setAttribute('l', '100');
square.setAttribute('c', 'red');
document.body.appendChild(square);
update.disabled = false;
remove.disabled = false;
add.disabled = true;
};
update.onclick = function() {
// Randomly update square's attributes
square.setAttribute('l', random(50, 200));
square.setAttribute('c', `rgb(${random(0, 255)}, ${random(0, 255)}, ${random(0, 255)})`);
};
remove.onclick = function() {
// Remove the square
document.body.removeChild(square);
update.disabled = true;
remove.disabled = true;
add.disabled = false;
};
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Life cycle callbacks test</title>
<style>
custom-square {
margin: 20px;
}
div{
border: 1px solid #ccc !important;
}
</style>
<script defer src="main.js"></script>
</head>
<body>
<h1>Life cycle callbacks test</h1>
<ul is="custom-square"></ul>
<div>
<button class="add">Add custom-square to DOM</button>
<button class="update">Update attributes</button>
<button class="remove">Remove custom-square from DOM</button>
</div>
</body>
</html>
*由shadow DOM里面添加的样式不会影响到外面,而且外面也不会影响里面。
参考: https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/Using_shadow_DOM
3. template slots
- template标签里的代码不会展现在页面中,知道js获取它的引用,然后添加到dom中,才会展示。
// html
<template id="my-paragraph">
<p>My paragraph</p>
</template>
// js
let template = document.getElementById('my-paragraph');
let templateContent = template.content;
document.body.appendChild(templateContent);
- 和web组件一起使用template
customElements.define('my-paragraph',
class extends HTMLElement {
constructor() {
super();
let template = document.getElementById('my-paragraph');
let templateContent = template.content;
//Node.cloneNode() 方法添加了模板(template) 的拷贝到阴影(shadow) 的根结点上.
const shadowRoot = this.attachShadow({mode: 'open'})
.appendChild(templateContent.cloneNode(true));
}
})
<template id="my-paragraph">
<style>
p {
color: white;
background-color: #666;
padding: 5px;
}
</style>
<p>My paragraph</p>
</template>
<my-paragraph></my-paragraph>
slot增加灵活度
- 我们将模板(tempalte) 的 p 标签改成下面这样
<p><slot name="my-text">My default text</slot></p>
<my-paragraph>
<span slot="my-text">Let's have some different text!</span>
</my-paragraph>
或者
<my-paragraph>
<ul slot="my-text">
<li>Let's have some different text!</li>
<li>In a list!</li>
</ul>
</my-paragraph>
- 这样就可以灵活的添加内容到模板中了。
栗子:
<!-- Learn about this code on MDN: https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/Using_templates_and_slots -->
<!DOCTYPE html>
<html>
<head>
<title>slot example</title>
<style>
dl { margin-left: 6px; }
dt { font-weight: bold; color: #217ac0; font-size: 110% }
dt { font-family: Consolas, "Liberation Mono", Courier }
dd { margin-left: 16px }
</style>
</head>
<body>
<template id="element-details-template">
<style>
details {font-family: "Open Sans Light",Helvetica,Arial}
.name {font-weight: bold; color: #217ac0; font-size: 120%}
h4 { margin: 10px 0 -8px 0; }
h4 span { background: #217ac0; padding: 2px 6px 2px 6px }
h4 span { border: 1px solid #cee9f9; border-radius: 4px }
h4 span { color: white }
.attributes { margin-left: 22px; font-size: 90% }
.attributes p { margin-left: 16px; font-style: italic }
</style>
<details>
<summary>
<span>
<code class="name"><<slot name="element-name">NEED NAME</slot>></code>
<i class="desc"><slot name="description">NEED DESCRIPTION</slot></i>
</span>
</summary>
<div class="attributes">
<h4><span>Attributes</span></h4>
<slot name="attributes"><p>None</p></slot>
</div>
</details>
<hr>
</template>
<element-details>
<span slot="element-name">slot</span>
<span slot="description">A placeholder inside a web
component that users can fill with their own markup,
with the effect of composing different DOM trees
together.</span>
<dl slot="attributes">
<dt>name</dt>
<dd>The name of the slot.</dd>
</dl>
</element-details>
<element-details>
<span slot="element-name">template</span>
<span slot="description">A mechanism for holding client-
side content that is not to be rendered when a page is
loaded but may subsequently be instantiated during
runtime using JavaScript.</span>
</element-details>
<script>
customElements.define('element-details',
class extends HTMLElement {
constructor() {
super();
const template = document
.getElementById('element-details-template')
.content;
const shadowRoot = this.attachShadow({mode: 'open'})
.appendChild(template.cloneNode(true));
}
})
</script>
</body>
</html>
- template外面的样式不会影响到里面的样式,里面也不会影响外面。