# web component

[web](/web/master.md) ⟩ component

{% hint style="success" %}
*<mark style="color:purple;">**Web component**</mark>* 讓你<mark style="color:yellow;">自定 HTML 元素</mark> ([custom element](/web/component/custom-element.md))，封裝 [HTML](/web/html.md) 結構、[CSS](/web/css.md) 樣式和 [JavaScript](/web/js.md) 行為。
{% endhint %}

{% tabs %}
{% tab title="🔴" %}

* [implementing components](/web/component/implement.md)：[custom element](/web/component/custom-element.md)╱[shadow DOM](/web/component/shadow-dom.md)╱[\<template>](/web/component/template.md)
* [using components](/web/component/use.md)
* [web component examples](/web/component/examples.md)
  {% endtab %}

{% tab title="💾" %}
📁 <mark style="color:yellow;">自訂元素</mark> ([custom element](/web/component/custom-element.md))

```javascript
// ⭐️ 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);
```

📁 <mark style="color:yellow;">自訂內建元素</mark> (<mark style="color:orange;">customized built-in element</mark>)

```javascript
// ⭐️ customized built-in element
class FancyButton extends HTMLButtonElement { 
    constructor(){...} 
}

// ⭐️ register new tag
//                    ╭─── name ───╮  ╭─ class ─╮  ╭───────⭐️────────╮
customElements.define('fancy-button', FancyButton, {extends: 'button'});
```

{% endtab %}

{% tab title="🛠" %}

* [Lit](https://lit.dev) (Google)
  {% endtab %}

{% tab title="📗" %}

* [ ] 📗 JS.info ⟩ [Web component](https://javascript.info/web-components)
* [ ] :green\_book: Building Native Web Components (2021)
* [ ] :green\_book: JavaScript: The Definitive Guide (15.6 Web Components)
* [ ] frameworks：Angular, [React](/web/react.md), Vue.js
* [ ] 📘 Google ⟩ Web Components ⟩&#x20;
  * [Custom Elements](https://developers.google.com/web/fundamentals/web-components/customelements)
  * [Shadow DOM](https://developers.google.com/web/fundamentals/web-components/shadowdom?hl=en)
  * [Tips & Tricks](https://developers.google.com/web/fundamentals/web-components/shadowdom#tricks)
  * [Best Practices](https://developers.google.com/web/fundamentals/web-components/best-practices)
    {% endtab %}

{% tab title="📘" %}

* MDN ⟩ &#x20;
  * [Web APIs](https://developer.mozilla.org/en-US/docs/Web/API) ⟩ [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) ⟩ [attachShadow()](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow)
  * [Web Components](https://developer.mozilla.org/en-US/docs/Web/Web_Components) ⟩
    * [Using shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM) ⭐️
      * [Using custom elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) ⭐️
      * [Using templates and slots](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_templates_and_slots)
      * [Using External Styles](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM#internal_versus_external_styles)
* Google ⟩ [Web components](https://developers.google.com/web/fundamentals/web-components)&#x20;
  * 📘 [Shadow DOM v1: Self-Contained Web Components](https://developers.google.com/web/fundamentals/web-components/shadowdom?hl=en)
    {% endtab %}

{% tab title="💈" %}

* [A Typewriter, but using a New HTML Tag](https://dev.to/auroratide/a-typewriter-but-using-a-new-html-tag-60i)
* [Calculator](/web/js/proj/calculator.md#v-3-componet)
* [element](/web/appendix/custom/element.md)
  {% endtab %}

{% tab title="👥" %}

* [client-side JavaScript timeline](/web/browser/event/client-side-javascript-timeline.md) - web components are not defined until fully loaded.
  {% endtab %}

{% tab title="❓ " %}
{% hint style="warning" %}
問：「[<mark style="color:blue;">`attachShadow()`</mark>](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow) 應該放在 [<mark style="color:blue;">`constructor()`</mark>](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#implementing_a_custom_element) 還是 [<mark style="color:blue;">`connectedCallback()`</mark>](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks)❓」
{% endhint %}

{% hint style="success" %}
答： :point\_right: [Gemini](https://g.co/gemini/share/785a07b0af65) , [ChatGPT](https://chatgpt.com/share/676cf842-9d34-800e-8d06-76bed175e03a),&#x20;
{% endhint %}
{% endtab %}
{% endtabs %}

## 宣告方式 <a href="#how-to-define" id="how-to-define"></a>

{% tabs %}
{% tab title="1️⃣ custom element" %}

### 1️⃣ **定義** custom element <a href="#define" id="define"></a>

先定義一個繼承 <mark style="color:blue;">HTMLElement</mark> 的 [custom element](/web/component/custom-element.md) ([class](/web/js/val/class.md))：

```javascript
// ⭐️ 1️⃣ custom element
class MyComponent extends HTMLElement {
  constructor() {
  
    // ⭐️ 呼叫父類別的 constructor
    super(); 

    // 在這裡初始化你的 component
  }
}
```

{% endtab %}

{% tab title="2️⃣ shadow DOM" %}

### 2️⃣ **建立 Shadow DOM** <a href="#shadow-dom" id="shadow-dom"></a>

[Shadow DOM](/web/component/shadow-dom.md) 為 *<mark style="color:purple;">**web component**</mark>* 建立一個<mark style="color:yellow;">封裝的 DOM 樹</mark> (<mark style="color:orange;">encapsulated DOM tree</mark>)，讓內部的樣式 ([CSS](/web/css.md) styles) 和腳本 ([JS](/web/js.md) scripts) 不會影響到外面 ([light DOM](/web/component/light-dom.md)) 的其他部分，反之亦然。

```javascript
constructor() {

  super();

  // ⭐️ 2️⃣ 建立 shadow DOM
  //   mode: 
  //   - 'open'  : 允許 JS 從外部訪問 shadow DOM
  //   - 'closed': 不允許
  this.attachShadow({ mode: 'open' }); 
}
```

{% endtab %}

{% tab title="3️⃣ template" %}
3️⃣ 建立樣板：以下提供<mark style="color:yellow;">三個方式</mark>建立 [\<template>](/web/component/template.md)

{% tabs %}
{% tab title="1: HTML" %}
📁 HTML：<mark style="color:yellow;">定義</mark> [<mark style="color:blue;">`<template>`</mark>](/web/component/template.md) 的 [HTML](/web/html.md) <mark style="color:yellow;">結構</mark>：

```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>
```

{% endtab %}

{% tab title="2: JS" %}
📁 JS：或將 3️⃣ 4️⃣ 兩步驟濃縮成這樣：

```javascript
// 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
```

{% endtab %}

{% tab title="3: static property" %}
📁 JS：或是將 [\<template>](/web/component/template.md) 放在[靜態屬性](/web/js/val/class/member/static.md)中：

```javascript
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 ...
	}
}
```

{% endtab %}
{% endtabs %}
{% endtab %}

{% tab title="4️⃣ shadow root" %}

### 4️⃣ 將樣板加入 Shadow DOM <a href="#append-template-content" id="append-template-content"></a>

<mark style="color:yellow;">複製樣板內容</mark>到 <mark style="color:orange;">shadow root</mark>：

```javascript
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️⃣ 兩個步驟濃縮成這樣：

```javascript
// 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
```

{% endtab %}

{% tab title="5️⃣ register" %}

### 5️⃣ 註冊 Component <a href="#register" id="register"></a>

用 <mark style="color:blue;">`customElements.define()`</mark> 註冊 *<mark style="color:purple;">**web component**</mark>*：

{% hint style="warning" %}
:star: 注意：*<mark style="color:purple;">**web component**</mark>* 的<mark style="color:yellow;">標籤名稱</mark> (<mark style="color:orange;">tag name</mark>) <mark style="color:red;">必須</mark><mark style="color:yellow;">包含連字號</mark> "<mark style="color:blue;">`-`</mark>":exclamation:
{% endhint %}

```javascript
// ⭐️ 5️⃣ 註冊 Component       ╭──tag name──╮  ╭─ class ─╮
window.customElements.define('my-component', MyComponent);
```

{% endtab %}
{% endtabs %}

## 使用方式 <a href="#how-to-use" id="how-to-use"></a>

{% tabs %}
{% tab title="自訂元素" %}
📁 HTML：<mark style="color:yellow;">使用</mark>自訂元素 ([custom element](/web/component/custom-element.md))

```markup
<!-- ⭐️ custom element -->
<my-component>
    <!-- slotted nodes -->
</my-component>
```

📁 JS：<mark style="color:yellow;">定義並宣告</mark>

```javascript
// ⭐️ 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);
```

{% endtab %}

{% tab title="自訂內建元素" %}
📁 JS：<mark style="color:yellow;">定義並宣告</mark>

```javascript
// ⭐️ customized built-in element
class FancyButton extends HTMLButtonElement { 
    constructor(){...} 
}

// ⭐️ register new tag
//                    ╭─── name ───╮  ╭─ class ─╮  ╭───────⭐️────────╮
customElements.define('fancy-button', FancyButton, {extends: 'button'});
```

📁 HTML：<mark style="color:yellow;">使用</mark>自訂內建元素 (<mark style="color:orange;">customized built-in element</mark>)

```markup
<!-- ⭐️ customized built-in element -->
<!--       ╭─── name ───╮           -->
<button is="fancy-button">some text</button>
```

📁 JS：<mark style="color:yellow;">使用</mark>自訂內建元素

```javascript
// ⭐️ 方法ㄧ： `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);
```

{% endtab %}

{% tab title="💈範例" %}

* [Calculator](/web/js/proj/calculator.md#v-3-componet) ❤️
  {% endtab %}
  {% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://lochiwei.gitbook.io/web/component.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
