Web Components
概念(来自 MDN )
-
作为开发者,我们都知道尽可能多的重用代码是一个好主意。这对于自定义标记结构来说通常不是那么容易 — 想想复杂的 HTML(以及相关的样式和脚本),有时你不得不写代码来呈现自定义 UI 控件,并且如果你不小心的话,多次使用它们会使你的页面变得一团糟。
-
Web Components 旨在解决这些问题 — 它由三项主要技术组成,它们可以一起使用来创建封装功能的定制元素,可以在你喜欢的任何地方重用,不必担心代码冲突。
- Custom element(自定义元素):一组 JavaScript API,允许你定义 custom elements 及其行为,然后可以在你的用户界面中按照需要使用它们。
- Shadow DOM(影子 DOM):一组 JavaScript API,用于将封装的“影子”DOM 树附加到元素(与主文档 DOM 分开呈现)并控制其关联的功能。通过这种方式,你可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
- HTML template(HTML 模板):
<template>
和<slot>
元素使你可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。
如何使用
- 首先需要准备一个
.html
文件和一个.js
文件,然后通过<script></script>
标签引入js
文件。
- HTML 文件
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Document</title>
<style>
body {
font-family: "STSong";
}
html {
--theme-color: orange;
}
h1 {
color: orange;
}
/*
注意:
1.自定义组件默认和span一样,所以需要设置 display:block
2.在外面只能修改自定义组件本身的样式,不能渗透到组件内部
*/
lf-card {
display: block;
margin: 15px;
margin-bottom: 0;
box-shadow: 0px 0px 10px orange;
}
</style>
</head>
<body>
<lf-card
data-name="Stephen"
data-position="后端开发工程师"
data-email="stephen@example.com"
></lf-card>
<lf-card
james
data-name="James"
data-position="前端开发工程师"
data-email="james@example.com"
></lf-card>
<script src="main.js"></script>
</body>
</html>
在上面的代码中,我们首先定义了一个自定义元素 lf-card
,然后使用 data-*
属性来传递数据给自定义元素。
- JS 文件
class LfCard extends HTMLElement {
// 返回需要监听变化的属性
static get observedAttributes() {
return ["data-name", "data-position"];
}
constructor() {
super();
/*
附加 shadowRoot
shadowRoot的作用:将组件的样式与页面样式隔离开
如果需要通过JS的 querySelector去查找元素,则需要通过
document.querySelector('自定义组件名').shadowRoot.querySelector('目标元素')
*/
this.attachShadow({ mode: "open" });
}
/*
生命周期
connectedCallback:当自定义元素第一次被连接到文档 DOM 时被调用。
disconnectedCallback:当自定义元素与文档 DOM 断开连接时被调用。
adoptedCallback:当自定义元素被移动到新文档时被调用。
attributeChangedCallback:当自定义元素的一个属性被增加、移除或更改时被调用。
*/
connectedCallback() {
this.render();
}
attributeChangedCallback(attr, oldVal, newVal) {
console.log(attr, oldVal, newVal);
if (oldVal) {
this.shadowRoot.querySelector(".name").textContent = newVal;
}
}
changeTransparent(n) {
this.setAttribute("data-name", "张三丰");
setTimeout(() => {
n.classList.add("transparent");
}, 1000);
}
render() {
// console.log(this.dataset); // 获取动态属性传过来的参数
let uName = this.dataset.name;
let uPosition = this.dataset.position;
let uEmail = this.dataset.email;
this.template = document.createElement("template");
this.template.innerHTML = `
<div class="business-card">
<div class="card-front">
<h1 class="name">${uName}</h1>
<h2>${uPosition}</h2>
<p class="company-name">创新科技未来有限公司</p>
<p>固定电话: 010-88** **88</p>
<p>Email: ${uEmail}</p>
<p>地址: 北京市梦幻城区银河东路99号</p>
</div>
</div>
`;
this.styles = document.createElement("style");
this.styles.innerHTML = `
/*
:host 代表的是标签本身,它的权重比外面的标签低,所以会被覆盖
*/
:host{
dissplay: block;
box-shadow: 0px 0px 10px deeppink;
}
:host([james]){
border-radius: 10px;
}
:host(:not([james])){
border: 5px solid orange;
}
/*
有些属性,默认是可以从外面页面继承的(如:font-family)
如果不想继承所有属性,可以设置all: initial
*/
:host{
all: initial;
}
.business-card {
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.06);
transition: transform 0.5s;
}
.business-card:hover {
transform: translateY(-3px) scale(1.01);
}
.card-front,.card-back {
padding: 15px;
box-sizing: border-box;
}
h1 {
margin: 0;
font-size: 24px;
/* 也可以支持自定义属性 */
color: var(--theme-color,#333);
text-shadow: none;
}
.last-name{ display: none; }
h2 {
margin: 0;
font-weight: 300;
color: #666;
margin-bottom: 10px;
font-size: 12px;
}
.company-name{
font-size: 18px;
color: #333;
margin-bottom: 15px;
}
p {
line-height: 1.4;
margin: 2px 0;
color: #666;
font-size: 14px;
}
.transparent{ opacity: .4; }
`;
this.shadowRoot.appendChild(this.template.content);
this.shadowRoot.appendChild(this.styles);
this.shadowRoot.querySelector(".name").addEventListener("click", (e) => {
console.log(this, e.target);
this.changeTransparent(e.target);
});
}
}
customElements.define("lf-card", LfCard);
结语
- Web Components 不仅仅是一项技术,它代表了网页开发的一种未来趋势——组件化、模块化。掌握它,就如同拥有了一个强大的工具箱,无论是在快速原型设计、提高代码复用性,还是在维护大型项目方面,都能让你如虎添翼。