用过Vue, React等现代前端框架的,对此概念会很熟悉:
Web Components 是一套不同的技术,允许您创建可重用的定制元素(它们的功能封装在您的代码之外)并且在您的 web 应用中使用它们。
与框架不同的是,它是原生,无任何依赖的组件方案。
先来看下Web Components的三个要素。
一组 JavaScript API,允许定义 custom elements 及其行为,然后可以在用户界面使用
下面是一个简单的示例:
UserCard.js
class UserCard extends HTMLElement {
constructor() {
super()
const container = document.createElement('div')
const p = document.createElement('h1')
p.innerHTML = this.getAttribute('name')
container.appendChild(p)
this.appendChild(container)
}
}
window.customElements.define('user-card', UserCard)
demo.html
<!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>Document</title>
</head>
<body>
<user-card name="Lenton"></user-card>
<script src="./UserCard.js"></script>
</body>
</html>
浏览中效果如下:
一组 JavaScript API,用于将封装的“影子”DOM 树附加到元素(与主文档 DOM 分开呈现)并控制其关联的功能。通过这种方式,可以保持元素的功能私有,可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
我们对上例的js做下改写:
class UserCard extends HTMLElement {
constructor() {
super()
const shadow = this.attachShadow({mode: 'closed'})
const container = document.createElement('div')
const p = document.createElement('h1')
p.innerHTML = this.getAttribute('name')
container.appendChild(p)
shadow.appendChild(container)
}
}
window.customElements.define('user-card', UserCard)
<template> 和 <slot> 元素使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用
如果所有dom结构都在JS类里用createElement创建,也太麻烦。template的出现就是为了解决这个问题。
继续改写示例:
demo.html, UserCard.js
<!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>Document</title>
</head>
<body>
<template id="userCard">
<h1>Lenton</h1>
<p>lenton@vip.qq.com</p>
</template>
<user-card name="Lenton"></user-card>
<script src="./UserCard.js"></script>
</body>
</html>
class UserCard extends HTMLElement {
constructor() {
super()
const shadow = this.attachShadow({mode: 'closed'})
const template = document.querySelector('#userCard')
const content = template.content.cloneNode(true)
shadow.appendChild(content)
}
}
window.customElements.define('user-card', UserCard)
文章以上部分就是对Web Components的整体简要介绍。有了对整体的大至了解后。就要开始正式学习了。
一个 custom element 的类对象可以通过 ES 2015 标准里的类语法生成
class UserCard extends HTMLElement {
constructor() {
super()
// 实现代码
}
}
使用时必需要注册。指定tag名称与关联的类。
注意: tag名称必须是以短横杠“-“分隔的单词,用于区分原生html tag.
window.customElements.define('user-card', UserCard)
然后添加组件内容:
添加一个h1标签,显示名称
添加一个p标签,显示邮箱
从组件属性中获取值并填充到相应位置
class UserCard extends HTMLElement {
constructor() {
super()
// const shadow = this.attachShadow({mode: 'closed'})
const container = document.createElement('div')
const name = document.createElement('h1')
const email = document.createElement('p')
container.appendChild(name)
container.appendChild(email)
// shadow.appendChild(container)
name.innerHTML = this.getAttribute('name') ?? '-'
email.innerHTML = this.getAttribute('email') ?? '-'
this.appendChild(container)
}
}
window.customElements.define('user-card', UserCard)
html部分:
<user-card name="Lenton" email="lenton@vip.qq.com"></user-card>
在 custom element 的构造函数中,可以指定多个不同的回调函数,它们将会在元素的不同生命时期被调用
connectedCallback:当 custom element 首次被插入文档 DOM 时,被调用。
disconnectedCallback:当 custom element 从文档 DOM 中删除时,被调用。
adoptedCallback:当 custom element 被移动到新的文档时,被调用。
attributeChangedCallback: 当 custom element 增加、删除、修改自身属性时,被调用。
改写代码,组件内显示名称随属性name值改变时改变
class UserCard extends HTMLElement {
constructor() {
super()
const container = document.createElement('div')
const name = document.createElement('h1')
const email = document.createElement('p')
container.appendChild(name)
container.appendChild(email)
name.innerHTML = this.getAttribute('name') ?? '-'
email.innerHTML = this.getAttribute('email') ?? '-'
this.appendChild(container)
}
static get observedAttributes() {
return ['name', 'email'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'name') {
this.querySelector('h1').innerHTML = newValue
}
}
}
window.customElements.define('user-card', UserCard)
现在页面如下:
在控制台执行一条改变name的脚本
document.querySelector('user-card').setAttribute('name', 'Alina')
可以看到页面变化
可以直接在组件内添加style标签来添加样式。
样式示例:
class UserCard extends HTMLElement {
constructor() {
super()
const container = document.createElement('div')
const name = document.createElement('h1')
const email = document.createElement('p')
container.appendChild(name)
container.appendChild(email)
name.innerHTML = this.getAttribute('name') ?? '-'
email.innerHTML = this.getAttribute('email') ?? '-'
this.appendChild(container)
this.updateStyle()
}
updateStyle() {
let style = this.querySelector('style')
if (!style) {
style = document.createElement('style')
this.appendChild(style)
}
style.textContent = `
h1{
color: green;
}
p{
color: blue;
}
`
}
}
window.customElements.define('user-card', UserCard)
页面效果如下
使用Shadow DOM示例
class UserCard extends HTMLElement {
constructor() {
super()
const shadow = this.attachShadow({mode: 'closed'})
const container = document.createElement('div')
const h1 = document.createElement('h1')
h1.innerHTML = 'Lenton'
container.appendChild(h1)
shadow.appendChild(container)
}
}
window.customElements.define('user-card', UserCard)
有两个要知道的部分:
<template> 包含一个 HTML 片段,不会在文档初始化时渲染。但是可以在运行时使用 JavaScript 显示。主要用作自定义元素结构的基础。
<slot> 一个占位符,你可以填充自己的标记,这样你就可以创建单独的 DOM 树并将它们呈现在一起。
先看一个示例:
demo.html, UserCard.js
<!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>Document</title>
</head>
<body>
<template id="userCard">
<h1>无名称</h1>
<slot></slot>
<p>无邮箱</p>
</template>
<user-card name="Lenton">
<p>这是<code>slot</code>的填充内容</p>
</user-card>
<script src="./UserCard.js"></script>
</body>
</html>
class UserCard extends HTMLElement {
constructor() {
super()
const shadow = this.attachShadow({mode: 'closed'})
const template = document.querySelector('#userCard')
const content = template.content.cloneNode(true)
shadow.appendChild(content)
shadow.querySelector('h1').innerHTML = this.getAttribute('name')
}
}
window.customElements.define('user-card', UserCard)
页面效果如下:
可以看到,会使用指定的template内容来生成组件。
同时,组件标签内的内容,会被插入到<template>的<slot>位置内。
未完待续...