🔰web component
web ⟩ component
Web component 讓你自定 HTML 元素 (custom element),封裝 HTML 結構、CSS 樣式和 JavaScript 行為。
📁 自訂元素 (custom element)
// ⭐️ 1️⃣ 宣告自訂元素
class MyComponent extends HTMLElement {
// constructor
constructor() {
super();
// ⭐️ 2️⃣ 建立 shadow DOM
this.attachShadow({ mode: 'open' }); // returns a reference to this.shadowRoot
// ⭐️ 3️⃣ 建立樣板 4️⃣ 複製樣板內容到 shadow root
this.shadowRoot.innerHTML = `
<style> ... </style>
<div class="container"> ... </div>
`;
}// end of constructor()
}// end of MyComponent
// ⭐️ 5️⃣ 註冊標籤 ╭──tag name──╮ ╭─ class ─╮
window.customElements.define('my-component', MyComponent);
📁 自訂內建元素 (customized built-in element)
// ⭐️ customized built-in element
class FancyButton extends HTMLButtonElement {
constructor(){...}
}
// ⭐️ register new tag
// ╭─── name ───╮ ╭─ class ─╮ ╭───────⭐️────────╮
customElements.define('fancy-button', FancyButton, {extends: 'button'});
Lit (Google)
client-side JavaScript timeline - web components are not defined until fully loaded.
問:「attachShadow()
應該放在 constructor()
還是 connectedCallback()
❓」
宣告方式
1️⃣ 定義 custom element
先定義一個繼承 HTMLElement 的 custom element (class):
// ⭐️ 1️⃣ custom element
class MyComponent extends HTMLElement {
constructor() {
// ⭐️ 呼叫父類別的 constructor
super();
// 在這裡初始化你的 component
}
}
2️⃣ 建立 Shadow DOM
Shadow DOM 為 web component 建立一個封裝的 DOM 樹 (encapsulated DOM tree),讓內部的樣式 (CSS styles) 和腳本 (JS scripts) 不會影響到外面 (light DOM) 的其他部分,反之亦然。
constructor() {
super();
// ⭐️ 2️⃣ 建立 shadow DOM
// mode:
// - 'open' : 允許 JS 從外部訪問 shadow DOM
// - 'closed': 不允許
this.attachShadow({ mode: 'open' });
}
3️⃣ 建立樣板:以下提供三個方式建立 <template>
📁 HTML:定義 <template>
的 HTML 結構:
<!-- ⭐️ 3️⃣ 建立樣板 -->
<template id="my-component-template">
<!---------- ⭐️ 內部的樣式 ---------->
<style>
.container {
border: 1px solid black;
padding: 10px;
}
</style>
<!---------- ⭐️ HTML 結構 ---------->
<div class="container">
<h1>Hello from My Component!</h1>
<!-- ⭐️ "unnamed slot":預留給使用者插入自訂內容 -->
<slot></slot>
</div>
</template>
📁 JS:或將 3️⃣ 4️⃣ 兩步驟濃縮成這樣:
// 1️⃣ 宣告 custom element
class MyComponent extends HTMLElement {
// constructor
constructor() {
super();
// 2️⃣ 建立 shadow DOM
this.attachShadow({ mode: 'open' }); // returns a reference to this.shadowRoot
// ⭐️ 3️⃣ 建立樣板 4️⃣ 複製樣板內容到 shadow root
this.shadowRoot.innerHTML = `
<style> ... </style>
<div class="container"> ... </div>
`;
}// end of constructor()
}// end of MyComponent
📁 JS:或是將 <template> 放在靜態屬性中:
class MyComponent extends HTMLElement {
// ⭐️ MyComponent.template
static get template() {
// 如果還沒初始化,執行初始化,然後將結果存到
// 私有靜態屬性(MyComponent._template)中
if (!this._template) { // this == MyComponent
// create <template> element
this._template = document.createElement("template");
// define template HTML structure
this._template.innerHTML = `...`;
}
return this._template;
}
// constructor
constructor() {
// ---- super init (HTMLElement) ----
super();
// ---- self init (MyComponent) ----
// ⭐️ attach shadow root
let root = this.attachShadow({ mode: "open" }); // root == this.shadowRoot
// ⭐️ clone template
let copy = MyComponent.template.content.cloneNode(true);
// ⭐️ add template copy to shadow root
root.appendChild(copy);
// other init process ...
}
}
4️⃣ 將樣板加入 Shadow DOM
複製樣板內容到 shadow root:
constructor() {
super();
this.attachShadow({ mode: 'open' }); // 2️⃣ 建立 shadow DOM
// 3️⃣ 建立 <template>
// ⭐️ 4️⃣ 複製樣板內容到 shadow root
const template = document.getElementById('my-component-template');
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
或將 3️⃣ 4️⃣ 兩個步驟濃縮成這樣:
// 1️⃣ 宣告 custom element
class MyComponent extends HTMLElement {
// constructor
constructor() {
super();
// 2️⃣ 建立 shadow DOM
this.attachShadow({ mode: 'open' });
// ⭐️ 3️⃣ 建立樣板 4️⃣ 複製樣板內容到 shadow root
this.shadowRoot.innerHTML = `
<style> ... </style>
<div class="container"> ... </div>
`;
}// end of constructor()
}// end of MyComponent
使用方式
📁 HTML:使用自訂元素 (custom element)
<!-- ⭐️ custom element -->
<my-component>
<!-- slotted nodes -->
</my-component>
📁 JS:定義並宣告
// ⭐️ autonomous custom element ---------------
// autonomous:獨立存在的(非內建的,如:HTMLButtonElement 等)
class MyComponent extends HTMLElement {
// constructor
constructor() {
// tell HTMLElement to initialize itself.
super();
// get `width`, `height` attributes
const height = this.getAttribute('height') || '100px';
const width = this.getAttribute('width') || '100px';
// ...
}
// connected to document
connectedCallback() {
// attach shadow root
let root = this.attachShadow({mode: 'open'});
// ⭐️ clone nodes from <template>
let clone = tmpl.content.cloneNode(true);
root.append( clone );
// ...
}
// template HTML
static template(height, width) {
return `
<style>
.placeholder {
background-color: pink;
width: ${height};
height: ${width};
}
</style>
<div class='placeholder'>Placeholder</div>
`;
}
}
// ⭐️ register <my-component>
// ⭐️⭐️ custom element names must contain a "hyphen".
// ╭──tag name──╮ ╭─ class ─╮
customElements.define('my-component', MyComponent);
📁 JS:定義並宣告
// ⭐️ customized built-in element
class FancyButton extends HTMLButtonElement {
constructor(){...}
}
// ⭐️ register new tag
// ╭─── name ───╮ ╭─ class ─╮ ╭───────⭐️────────╮
customElements.define('fancy-button', FancyButton, {extends: 'button'});
📁 HTML:使用自訂內建元素 (customized built-in element)
<!-- ⭐️ customized built-in element -->
<!-- ╭─── name ───╮ -->
<button is="fancy-button">some text</button>
📁 JS:使用自訂內建元素
// ⭐️ 方法ㄧ: `new MyComponent()`
let button = new FancyButton();
// ⭐️ 方法二: `document.createElement('my-component')`
// ╭─tag──╮ ╭────────⭐️────────╮
let button = document.createElement('button', {is: 'fancy-button'});
// customize
button.textContent = 'Fancy button!';
button.disabled = true;
// add to DOM tree
document.body.appendChild(button);
Calculator ❤️
Last updated