今天要來紀錄如何建立及使用 Web Component。
Web Component 是一個概念,
指在建立一個可重覆利用、並且擁有獨立於全域
CSS, Javascript 影響的自定義元素。
要建立 Web Component
通常會用到3項技術:
- Custom Element (自定義元素)
- Shadow DOM (影子 DOM)
- HTML Template (HTML 模板)
直接來看例子,例子中也寫了說明的註解:
HTML:
<div id="aDiv">Normal DOM</div>
<!-- 使用設定好了的 Custom Element -->
<my-custom-dom>
<div slot="slot1">Slot 1</div>
<div slot="slot2">Slot 2</div>
<div>No Slot</div>
</my-custom-dom>
<!-- 設定等下要給 Shadom DOM 用的 template -->
<template id="my-template">
<!-- Shadom DOM -->
<style>
div {
background-color: #00FF00;
}
</style>
<!-- 雖然 id="aDiv" 上面已經有 DOM 用過了,
但因為是在 Shadom DOM 裡面,所以互相獨立不衝突 -->
<div id="aDiv">Custom DOM</div>
<!-- <slot> 雖然會內嵌在 Custom DOM 中顯示,但是本體是處在 Shadom DOM 之外
,所以不會受到 Shadow DOM 的 CSS 影響,
,而是會受到 Shadom DOM 外部的 CSS 影響-->
<div><slot name="slot1"></slot></div> <!-- <div slot="slot1"> 會顯示在這裡 -->
<slot name="slot2"></slot> <!-- <div slot="slot"> 會顯示在這裡 -->
<slot></slot> <!-- 沒設定 slot 屬性的 <div> 會顯示在這裡 -->
</template>
CSS:
/* 不會作用到 Shadom DOM 裡面 */
div {
color: #0000FF;
background-color: #FF0000;
}
JS:
class MyCustomDom extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
//在這裡 shadowRoot == this.shadowRoot
var shadowRoot = this.attachShadow({
mode: "open"
});
shadowRoot.appendChild(document.getElementById("my-template").content);
//也可以不用 HTML 上的 <template>,
//改用 Javascript 自己建立 <template>,範例如下:
/*
var template = document.createElement('template');
template.innerHTML = `
<style> ... some style </style>
<div id='aDiv'>Custom DOM</div> ... some DOM, some <slot>
`;
shadowRoot.appendChild(template.content);
*/
//我們在這也可以把 shadowRoot 當做像是 shadow DOM 裡面的 document來用,
//add 一些 eventListener、修改一下 DOM Element 等
shadowRoot.getElementById("aDiv").addEventListener("click", function() {
console.log("Clicked!!");
});
}
}
customElements.define("my-custom-dom", MyCustomDom);
我們可以用 class extends HTMLElement (或是任何 extends 了 HTMLElement
的類別,例如 HTMLParagraphElement ) 的方式定義一個 Custom
Element,例如像這樣:
class MyCustomElement extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
console.log("Custom Element 被添加至頁面。");
}
disconnectedCallback() {
console.log("Custom Element 從頁面中被移除。");
}
adoptedCallback() {
console.log("Custom Element 被移動至新頁面。");
}
attributeChangedCallback(name, oldValue, newValue) {
console.log(`屬性 ${name} 的值已從 ${oldValue} 被變更成 ${newValue}。`);
}
}
//使用 Window.customElements 的 define(customElementName, customElementClass) 來註冊 Custome Element,
//customElementName 要用全小寫、使用至少一個分隔符 "-" 分隔的命名方式
customElements.define("my-custom-element", MyCustomElement);
HTMLElement class 裡提供了幾個如上述的 callback function,通常我們會在 connectedCallback() 裡設定 Shadow DOM 並將其掛載到 Custom Element 中。
在 Custom Element class 裡,我們可以使用 var shadowRoot = this.attachShadow({mode: "open"}); 來掛載一個 Shadow DOM ,
如果 mode 選 open ,則 Shadow DOM 可以被外界用 CustomElement.shadowRoot 的方式存取,也就是 Shadow DOM 會以 shadowRoot 的屬性名被設定到 Custom Element class 上,
即 this.shadowRoot 等於剛剛 var shadowRoot = this.attachShadow({mode: "open"}); 返回的 shadowRoot。
如果 mode 選 closed 的話,外界就無法存取 Shadow DOM,即 this.shadowRoot == null 。
shadowRoot.appendChild(Element) 可以將想要的 Element 放入 ShadowRoot 中,
例如:
var shadowRoot = this.attachShadow({ mode: "open" });
var span = document.createElement("span");
span.textContent = "Test";
shadowRoot.appendChild(span);
如果使用 HTML Template 的話,會更方便的設定 Element 的內容, css 等,例如:
var shadowRoot = this.attachShadow({ mode: "open" });
var template = document.createElement('template');
template.innerHTML = `
<style>
div {
color: #0000FF;
}
</style>
<div>Test</div>
<span>Test</span>
`;
shadowRoot.appendChild(template.content);
HTML Template 也可以先設定在頁面中,再用 Javascript 讀取,例如:
HTML:
<template id="my-template">
<style>
div {
color: #0000FF;
}
</style>
<div>Test</div>
<span>Test</span>
</template>
JS:
var shadowRoot = this.attachShadow({ mode: "open" });
var template = document.querySelector("#my-template");
shadowRoot.appendChild(template.content);
將 Element 放入 Shadom DOM 後,我們就可以用 shadowRoot 來取得 Shadom DOM 中的元素來操作,例如:
shadowRoot.appendChild(template.content);
shadowRoot.querySelector("div").addEventListener("click", (event) => {
event.target.innerHTML = "Clicked";
});
Shadow DOM 可以配合 Slot 顯示從外部嵌入的 HTML ,但要注意的是,Slot 本體雖然在 Custom Element 中,但並不會被放到 Shadow DOM 中,
所以 Slot 只會被 Shadow DOM 外部的 CSS 影響,不會被 Shadow DOM 內的 CSS 所影響,
以上述最一開始的例子來說,可以參考下方 Chrome 的結構圖,
可以看到寫在 <my-custom-dom> 中的 <div slot> 和 <div> 等最後都沒有被放到 Shadow DOM 裡,而是放在 <my-custom-dom> 中但在 Shadow DOM 外部,
所以只會吃到 Shadow DOM 外部的 CSS,Shadow DOM 只是把 Slot 內嵌顯示在其中而已。
參考資料:
-
Web Component - Web API | MDN
-
iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天