網頁元件的優點在於可重複使用:您只需建立一次 UI 小工具,即可多次重複使用。儘管您需要 JavaScript 才能建立網頁元件,但不需要 JavaScript 程式庫。HTML 和相關 API 能提供您需要的所有資訊。
網頁元件標準由三個部分組成:HTML 範本、自訂元素和 Shadow DOM。只要結合使用,您就能建構可順利整合到現有應用程式的自訂獨立元素 (封裝) 和可重複使用的元素,就像我們先前介紹的所有其他 HTML 元素一樣。
在本節中,我們會建立 <star-rating>
元素,讓使用者能夠以一到五顆星的分數為體驗評分。為自訂元素命名時,建議全部使用小寫英文字母。此外,請加入破折號,以便區分一般和自訂元素。
我們會說明如何使用 <template>
和 <slot>
元素、slot
屬性和 JavaScript 建立包含封裝的 Shadow DOM 範本。接下來,我們會重複使用定義的元素,並自訂一段文字,就如同您為任何元素或網頁元件一樣。我們也會簡短說明如何在自訂元素內部和外部使用 CSS。
<template>
元素
<template>
元素用於宣告要複製的 HTML 片段,並使用 JavaScript 插入 DOM。根據預設,系統不會顯示元素的內容。而是使用 JavaScript 例項化。
<template id="star-rating-template">
<form>
<fieldset>
<legend>Rate your experience:</legend>
<rating>
<input type="radio" name="rating" value="1" aria-label="1 star" required />
<input type="radio" name="rating" value="2" aria-label="2 stars" />
<input type="radio" name="rating" value="3" aria-label="3 stars" />
<input type="radio" name="rating" value="4" aria-label="4 stars" />
<input type="radio" name="rating" value="5" aria-label="5 stars" />
</rating>
</fieldset>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</form>
</template>
由於 <template>
元素的內容並未寫入畫面,因此不會顯示 <form>
及其內容。是的,這個 Codepen 沒有內容,但當你檢查 HTML 分頁時,就會看到 <template>
標記。
在這個範例中,<form>
不是 DOM 中 <template>
的子項。相反地,<template>
元素的內容是由 HTMLTemplateElement.content
屬性傳回的 DocumentFragment
子項。為了使系統顯示,必須使用 JavaScript 擷取內容,並將這些內容附加至 DOM。
這個簡短的 JavaScript 並未建立自訂元素。這個範例會將 <template>
的內容附加到 <body>
中。內容已成為可見且可設定樣式的 DOM 的一部分。
需要 JavaScript 只實作一個星級評等的範本並不是最實用的用途,但是針對重複使用的可自訂星級評等小工具建立網頁元件,便相當實用。
<slot>
元素
我們納入一個版位,以包含自訂每個例項的圖例。HTML 會在 <template>
內提供 <slot>
元素做為預留位置,並在提供名稱時建立「已命名的版位」。已命名版位可用來自訂網頁元件中的內容。<slot>
元素可讓我們控制自訂元素的子項應插入其陰影樹狀結構的位置。
在我們的範本中,我們會將 <legend>
變更為 <slot>
:
<template id="star-rating-template">
<form>
<fieldset>
<slot name="star-rating-legend">
<legend>Rate your experience:</legend>
</slot>
如果元素的 slot 屬性值與已命名版位的名稱相符,則 name
屬性用於將版位指派給其他元素。如果自訂元素與版位沒有相符項目,則會顯示 <slot>
的內容。
因此,我們納入了包含一般內容的 <legend>
,即使任何人只要在 HTML 中加入 <star-rating></star-rating>
(但沒有內容),都能正常顯示。
<star-rating>
<legend slot="star-rating-legend">Blendan Smooth</legend>
</star-rating>
<star-rating>
<legend slot="star-rating-legend">Hoover Sukhdeep</legend>
</star-rating>
<star-rating>
<legend slot="star-rating-legend">Toasty McToastface</legend>
<p>Is this text visible?</p>
</star-rating>
slot 屬性是全域屬性,用於取代 <template>
中的 <slot>
內容。在自訂元素中,具有版位屬性的元素為 <legend>
。但您不一定要這麼做。在範本中,<slot name="star-rating-legend">
會替換為 <anyElement slot="star-rating-legend">
,其中 <anyElement>
可以是任何元素,甚至是其他自訂元素。
未定義的元素
在 <template>
中,我們使用了 <rating>
元素。這不是自訂元素。而是未知元素。瀏覽器無法識別某個元素時也不會失敗。瀏覽器會將無法辨識的 HTML 元素視為匿名內嵌元素,該元素可透過 CSS 設定樣式。與 <span>
類似,<rating>
和 <star-rating>
元素沒有使用者代理程式套用的樣式或語意。
請注意,系統不會轉譯 <template>
和內容。<template>
是已知的元素,其中包含不顯示的內容。尚未定義 <star-rating>
元素。在我們定義元素前,瀏覽器會像所有無法辨識的元素一樣顯示。目前,無法辨識的 <star-rating>
會視為匿名內嵌元素,因此會顯示第三個 <star-rating>
中圖例和 <p>
的內容,與這些內容位於 <span>
中一樣。
我們可以定義 元素,將此無法辨識的元素轉換為自訂元素。
自訂元素
需要 JavaScript 才能定義自訂元素。定義後,<star-rating>
元素的內容會由陰影根取代,其中包含我們將相關聯範本的所有內容。範本中的 <slot>
元素會替換為 <star-rating>
中元素的內容,其中 slot
屬性值與 <slot>
的名稱值相符 (如果有的話)。否則,系統會顯示範本版位的內容。
與版位相關聯的自訂元素內容 (我們的第三個 <star-rating>
中的 <p>Is this text visible?</p>
) 不會納入陰影根層級,因此不會顯示。
我們透過擴充 HTMLElement
來定義名為 star-rating
的自訂元素:
customElements.define('star-rating',
class extends HTMLElement {
constructor() {
super(); // Always call super first in constructor
const starRating = document.getElementById('star-rating-template').content;
const shadowRoot = this.attachShadow({
mode: 'open'
});
shadowRoot.appendChild(starRating.cloneNode(true));
}
});
現在,該元素已定義完畢,每次瀏覽器遇到 <star-rating>
元素時,就會以元素定義的 #star-rating-template
(這是我們的範本) 定義呈現。瀏覽器會將陰影 DOM 樹狀結構附加至節點,將範本內容的「複製」附加至該 shadow DOM。請注意,您attachShadow()
可以使用的元素受到限制。
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(starRating.cloneNode(true));
如果您查看開發人員工具,就會留意 <template>
的 <form>
是每個自訂元素的陰影根部分。在開發人員工具中的每個自訂元素中,<template>
內容的複本都會顯示,並且會顯示在瀏覽器中,但自訂元素本身的內容不會顯示在畫面上。
在 <template>
範例中,我們將範本內容附加至文件主體,並將內容新增至一般 DOM。在 customElements
定義中,我們使用相同的 appendChild()
,但複製的範本內容已附加至封裝的 shadow DOM。
您是否注意到,星星回到未設定樣式的圓形按鈕?Codepen CSS 分頁中的樣式屬於 shadow DOM 的一部分,而非標準 DOM。該分頁的 CSS 樣式的範圍僅限於文件,而非 shadow DOM,因此系統不會套用樣式。我們必須建立封裝的樣式,為封裝的 Shadow DOM 內容設定樣式。
陰影 DOM
陰影 DOM 將 CSS 樣式設定至每個陰影樹狀結構,隔離文件的其餘部分。也就是說,外部 CSS 不適用於您的元件,而元件樣式對於文件的其餘部分不會產生任何影響,除非我們刻意將這類樣式導向至您的元件。
由於我們已將內容附加到 shadow DOM,因此可以加入 <style>
元素,將封裝的 CSS 提供給自訂元素。
只要將範圍限定在自訂元素,我們就不必擔心文件其他部分顯示的樣式。我們可以大幅減少選取器的明確性。舉例來說,由於自訂元素中唯一使用的輸入是圓形按鈕,因此我們可以使用 input
而非 input[type="radio"]
做為選取器。
<template id="star-rating-template">
<style>
rating {
display: inline-flex;
}
input {
appearance: none;
margin: 0;
box-shadow: none;
}
input::after {
content: '\2605'; /* solid star */
font-size: 32px;
}
rating:hover input:invalid::after,
rating:focus-within input:invalid::after {
color: #888;
}
input:invalid::after,
rating:hover input:hover ~ input:invalid::after,
input:focus ~ input:invalid::after {
color: #ddd;
}
input:valid {
color: orange;
}
input:checked ~ input:not(:checked)::after {
color: #ccc;
content: '\2606'; /* hollow star */
}
</style>
<form>
<fieldset>
<slot name="star-rating-legend">
<legend>Rate your experience:</legend>
</slot>
<rating>
<input type="radio" name="rating" value="1" aria-label="1 star" required/>
<input type="radio" name="rating" value="2" aria-label="2 stars"/>
<input type="radio" name="rating" value="3" aria-label="3 stars"/>
<input type="radio" name="rating" value="4" aria-label="4 stars"/>
<input type="radio" name="rating" value="5" aria-label="5 stars"/>
</rating>
</fieldset>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</form>
</template>
雖然網頁元件已封裝在 <template>
中的標記,而 CSS 樣式的範圍限定為 shadow DOM,而且在元件外隱藏,所以轉譯的運算單元內容 (<star-rating>
的 <anyElement slot="star-rating-legend">
部分) 不會封裝。
在目前範圍外設定樣式
您也可以直接在 shadow DOM 中設定文件樣式,以及根據全域樣式設定 shadow DOM 的內容樣式。陰影邊界 (陰影 DOM 結束且一般 DOM 開始) 可以周遊,但只有非常刻意。
陰影樹狀結構是 shadow DOM 中的 DOM 樹狀結構。陰影根是陰影樹狀結構的根節點。
:host
虛擬類別會選取 <star-rating>
(陰影主機元素)。「陰影主機」是陰影 DOM 附加的 DOM 節點。如果只要指定主機的特定版本,請使用 :host()
。這只會選取與傳遞的參數相符的陰影主機元素,例如類別或屬性選取器。如要選取所有自訂元素,您可以在全域 CSS 中使用 star-rating { /* styles */ }
,或在範本樣式中使用 :host(:not(#nonExistantId))
。具體性來說,全球 CSS 供應商勝出。
::slotted()
虛擬元素會��� shadow DOM ������陰影 DOM 邊界。如果該元素與選取器相符,就會選取版位化元素。在本範例中,::slotted(legend)
與三個圖例相符。
如要從全域範圍指定 CSS 的 shadow DOM,您必須修改範本。您可以將 part
屬性新增至要設定樣式的任何元素。接著,使用 ::part()
虛擬元素,比對陰影樹狀結構中與傳遞的參數相符的元素。虛擬元素的錨定或來源元素是主機或自訂元素名稱,在本例中為 star-rating
。參數是 part
屬性的值。
如果範本標記的開頭為:
<template id="star-rating-template">
<form part="formPart">
<fieldset part="fieldsetPart">
我們可以將 <form>
和 <fieldset>
指定如下:
star-rating::part(formPart) { /* styles */ }
star-rating::part(fieldsetPart) { /* styles */ }
部分名稱的運作方式與類別類似:元素可以有多個以空格分隔的部分名稱,而多個元素可以具有相同的部分名稱。
Google 提供了很棒的建立自訂元素檢查清單。我們也建議您瞭解「宣告式陰影 DOM」。
隨堂測驗
測驗您對範本、版位和陰影的相關知識。
根據預設,shadow DOM 外部的樣式會為其中的元素設定樣式。
以下何者是 <template>
元素的正確說明?