前言
看完本文你会得到:
- 什么是webComponents
- webComponents slot 传递
- webComponents 样式隔离
- webComponents 属性监听
- webComponents 事件冒泡传递
基本概念
Web Components是一种用于构建可重用、独立且封装的前端组件的技术。它由一组Web标准组成,包括自定义元素、影子DOM和HTML模板等。通过使用Web Components,开发人员可以创建具有自定义功能和样式的HTML元素,并将其封装为独立的组件,以便在不同的项目中进行重复使用。
废话不多说!让我们直接走起~
基础目录
.
├── example
│ ├── bigbang.js
│ ├── index.html
│ ├── main.css
│ ├── main.js
│ └── package.json
初始化组件
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>组件初始化</title>
<script src="./bigbang.js" type="module"></script>
</head>
<body>
<big-bang></big-bang>
</body>
</html>
bigbang.js
class BigBang extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: "closed" });
let div = document.createElement("div");
div.textContent = "Hello, BigBang!";
shadowRoot.append(div)
}
}
customElements.define("big-bang", BigBang);
预览
slot 传递
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>组件初始化</title>
<script src="./bigbang.js" type="module"></script>
</head>
<body>
<big-bang>
<h2 slot="title">我是标题</h2>
<ul slot="list">
<li>测试1</li>
<li>测试2</li>
<li>测试3</li>
</ul>
</big-bang>
</body>
</html>
bigbang.js
const template = document.createElement("template");
template.innerHTML = `
<div>
<h1>hello 我是测试</h1>
<slot name="title">我是默认的插槽【标题】</slot>
<slot name="list">我是默认的插槽【列表】</slot>
</div>
`;
class BigBang extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: "closed" });
let clone = template.content.cloneNode(true);
shadowRoot.append(clone);
}
}
customElements.define("big-bang", BigBang);
预览
详细代码参考
绑定样式
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>组件初始化</title>
<script src="./bigbang.js" type="module"></script>
</head>
<body>
<big-bang>
<h2 slot="title">我是标题</h2>
<ul slot="list">
<li>测试1</li>
<li>测试2</li>
<li>测试3</li>
</ul>
</big-bang>
</body>
</html>
main.css
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
html {
font-size: 20px;
color-scheme: dark light;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}
header,
main {
padding: 1rem 4rem;
}
h2 {
color: cornflowerblue;
}
h3 {
border-bottom: 2px solid white;
}
div {
border: 1px solid #000;
}
/* 对外控制组件中的样式 */
::part(demo) {
color: red;
}
Tips:不能指定复合选择器 = > ::part(demo) span
bigbang.css
.big-bang-box {
border: 1px solid blue;
}
bigbang.js
const template = document.createElement("template");
template.innerHTML = `
<style>
@import url('./bigbang.css');
.big-bang-box {
border: 1px solid red;
padding:3rem;
margin:3rem;
}
.big-bang-box h1 {
color: #fff;
}
:host-context(main) {
background-color: red;
}
:host(big-bang) {
background-color: #d698dd;
display: block;
}
/* shadow 专门的选择器 */
:host {
background-color: aliceblue;
display: block;
}
/* ::slotted 选择器用于slot。 */
::slotted(h2) {
font-size: 4rem;
background-color: #322533;
color: #fff !important;
}
/* 不能这样用 */
slot {
}
</style>
<div class='big-bang-box'>
<h1 part="demo">我是Web Components <span>111</span> </h1>
<slot name="title">我是默认的插槽【标题】</slot>
</div>
`;
class BigBang extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: "closed" });
let clone = template.content.cloneNode(true);
shadowRoot.append(clone);
}
}
customElements.define("big-bang", BigBang);
预览
样式的控制可以在当前自定义的组件里面声明<style>标签控制当前的样式,类似于vue组件中的scoped,如果想从外面改变当前组件element元素的样式可以使用::part,但是不支持复合元素,ShadowDOM 提供了当前组件专有的选择器:host、:host-context进行组件的使用;
:host选择器:
它用于选择Shadow DOM树的根元素,即Shadow Root的宿主元素。可以将:host理解为表示当前Shadow DOM树的根节点。通过:host选择器,我们可以对整个Shadow DOM树的根节点应用样式。 示例:
:host {
background-color: red;
}
:host-context选择器:
它用于选择Shadow DOM树的宿主元素及其所有祖先元素,只要这些祖先元素满足指定的条件。:host-context可以看作是从Shadow DOM树中向外进行选择的操作。 示例:
:host-context(.container) {
background-color: blue;
}
注意:
:host 选择器是 Shadow 专门的选择器,用于选择 Shadow DOM 的根元素。host的优先级如下: :host < :host(big-bang) || :host-context(main):host(big-bang) || :host-context(main) 取决于谁写在后面,谁的优先级高
参考资料:
- :host - CSS:层叠样式表 | MDN
- :host-context() - CSS:层叠样式表 | MDN
- ::slotted() - CSS: Cascading Style Sheets | MDN
- ::part() - CSS: Cascading Style Sheets | MDN
- template:内容模板元素 - HTML(超文本标记语言) | MDN
详细代码参考
绑定属性
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>属性监听</title>
<link rel="stylesheet" href="./main.css" />
<script src="./bigbang.js" type="module"></script>
<script src="./main.js" type="module"></script>
</head>
<body>
<big-bang character="Leonard" color="cornflowerblue">
<h2 slot="title">加载标题</h2>
</big-bang>
</body>
</html>
bigbang.js
const template = document.createElement("template");
template.innerHTML = `
<style>
:host{
/* the shadow root */
background-color: #333; /* default */
color: white;
display: block; /* critical */
}
::slotted(h2){
/* represents an h2 element that has been placed into a slot */
font-weight: 300;
}
.root{
position: relative;
padding: 2rem;
}
.character{
position: absolute;
z-index: 10;
top: -10rem;
left: 0;
font-size: 10rem;
line-height:1;
color: hsla(60, 50%, 80%, 0.32);
}
</style>
<div class="root">
<h1>Big Bang Theory</h1>
<slot name="title">我是默认的插槽【标题】</slot>
</div>
`;
class BigBang extends HTMLElement {
constructor() {
super();
this.root = this.attachShadow({ mode: "closed" });
let clone = template.content.cloneNode(true);
this.root.append(clone);
}
static get observedAttributes() {
return ["character", "color"];
}
get character() {
return this.getAttribute("character");
}
set character(value) {
// 可以进行数据过滤处理~
this.setAttribute("character", value);
}
get color() {
return this.getAttribute("color");
}
set color(value) {
// 可以进行数据过滤处理~
this.setAttribute("color", value);
}
attributeChangedCallback(name, oldValue, newValue) {
if (name.toLowerCase() === "character") {
const div = this.root.querySelector(".root");
let p = div.querySelector("p")
? div.querySelector("p")
: document.createElement("p");
p.className = "character";
p.textContent = newValue;
div.append(p);
}
if (name.toLowerCase() === "color") {
this.style.backgroundColor = newValue;
}
}
}
customElements.define("big-bang", BigBang);
// <big-bang>
预览
根据上面的代码 我们定义了character和color 两个属性,然后通过attributeChangedCallback 去监听,当前传入的状态,紧接着在最外层找到当前的组件big-bang 点击进行属性值切换就得到了以上的效果
参考资料:
详细代码参考
事件通信
index.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>事件通信</title>
<link rel="stylesheet" href="./main.css" />
<script src="./main.js"></script>
<script src="./bigbang.js" type="module"></script>
</head>
<body>
<main>
<h3>传递参数如下:</h3>
<h4></h4>
<br/>
<big-bang color="lightcoral" action="goodbye">
<h2 slot="title">It all started with...</h2>
<span slot="done">Ok</span>
</big-bang>
</main>
</body>
</html>
bigbang.js
const template = document.createElement("template");
template.innerHTML = `
<style>
:host{
/* the shadow root */
background-color: #333; /* default */
color: white;
display: block; /* critical */
}
::slotted(h2){
/* represents an h2 element that has been placed into a slot */
font-weight: 300;
}
.root{
position: relative;
padding: 2rem;
}
button{
font-size: 1.2rem;
border: none;
background-color: #222;
color: #eee;
padding: 0.25rem 2rem;
cursor: pointer;
}
button:active{
background-color: #eee;
color: #222;
}
</style>
<div class="root">
<h1>Big Bang Theory</h1>
<slot name="title">我是默认的插槽【标题】</slot>
<p>
<button><slot name="done"></slot></button>
</p>
</div>
`;
class BigBang extends HTMLElement {
constructor() {
super();
this.root = this.attachShadow({ mode: "closed" });
let clone = template.content.cloneNode(true);
this.root.append(clone);
let btnSlot = this.root.querySelector("slot[name=done]");
let htmlSlot = btnSlot.assignedNodes()[0]; // 找到分配给Slot的元素
if (htmlSlot) {
btnSlot.addEventListener("slotchange", (ev) => {
console.log(htmlSlot, "===>slotchange");
});
btnSlot.parentElement.addEventListener("click", (ev) => {
let action =
this.action && typeof window[this.action] === "function"
? window[this.action]
: this.defaultActionForBigBangButton;
action('我是传递的参数');
});
} else {
btnSlot.parentElement.remove();
}
}
defaultActionForBigBangButton() {
console.log("Missing a VALID action attribute value");
}
// 加载
connectedCallback() {
console.log("added to page");
if (this.color) {
this.color = "red";
}
}
// 卸载
disconnectedCallback() {
console.log("removed from page");
}
// Attributes and Properties...
static get observedAttributes() {
return ["color", "action"];
}
get color() {
return this.getAttribute("color");
}
set color(value) {
this.setAttribute("color", value);
}
get action() {
return this.getAttribute("action");
}
set action(value) {
this.setAttribute("action", value);
}
get customAttribute() {
return this.getAttribute("customAttribute");
}
set customAttribute(value) {
this.setAttribute("customAttribute", value);
}
attributeChangedCallback(attributeName, oldVal, newVal) {
console.log(attributeName,'===>attributeName')
if (attributeName.toLowerCase() === "color") {
this.style.backgroundColor = newVal;
}
}
}
customElements.define("big-bang", BigBang);
// <big-bang>
-
main.js
document.addEventListener('DOMContentLoaded', () => {
});
function hello(ev) {
console.log(ev);
}
function goodbye(value) {
let bb = document.querySelector('big-bang');
let h4 = document.querySelector('h4');
h4.textContent = value
bb.remove()
}
预览
通过执行big-bang组件的action属性执行了全局方法:goodbye 进行了数据通信,并且执行了整个组件的删除操作,也可以使用slotchange方法来自定义监听插槽变化,实现一些逻辑处理
参考资料:
- Using custom elements - Web APIs | MDN
- HTMLSlotElement: slotchange event - Web APIs | MDN
- HTMLSlotElement: assignedNodes() method - Web APIs | MDN
详细代码参考
结语
以上就是整个webComponents的演示,代码逻辑比较简单,感兴趣的小伙伴可以自行运行或者访问源码进行clone实现,如果整篇能帮助你的话记得点赞收藏哦~ 再见了 老baby们!