範本、版位和陰影

網頁元件的優點在於可重複使用:您只需建立一次 UI 小工具,就能重複使用。當您時 需要使用 JavaScript 建立網頁元件,不需要 JavaScript 程式庫。您需要的一切,HTML 與相關聯的 API 都能提供。

Web Component 標準包含三個部分:HTML 範本Custom Elements (自訂元素) 和 Shadow DOM。 結合運用,您可以建構能順暢整合的獨立式 (封裝) 可重複使用的元素 就如同先前介紹的所有其他 HTML 元素

在本節中,我們會建立 <star-rating> 元素,這個網頁元件可讓使用者為網站上的體驗評分 滿 1 到 5 顆星為自訂元素命名時,建議全部使用小寫英文字母。此外,也要加上破折號 以便區分一般和自訂元素

我們將討論如何使用 <template><slot> 元素、slot 屬性以及 JavaScript 建立採用的範本 一個封裝的 Shadow DOM。我們會重複使用已定義的元素、自訂一段文字 例如元素或網頁元件我們也會簡要討論如何在自訂元素內部和外部使用 CSS。

<template> 元素

<template> 元素可用來宣告要透過 JavaScript 複製並插入 DOM 的 HTML 片段。根據預設,系統不會轉譯元素的內容。而是使用 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。

先前轉碼器的螢幕截圖,如 DOM 所示。

要求 JavaScript 只使用一個星級評等的範本並不實用,但為單一星級評等建立網路元件是不夠的 而可自訂的星級評等小工具相當實用。

<slot> 元素

我們納入了每個出現次數圖例的自訂版位。HTML 提供 <slot> 做為預留位置的 <template> 元素,如果提供名稱,即可建立一個「已命名的版位」。可使用已命名的版位 自訂網頁元件中的內容<slot> 元素可讓我們控制自訂的子項位置 元素應插入其陰影樹狀結構中。

在範本中,我們將 <legend> 變更為 <slot>

<template id="star-rating-template">
  <form>
    <fieldset>
      <slot name="star-rating-legend">
        <legend>Rate your experience:</legend>
      </slot>

如果元素的版位屬性值符合name 命名版位的名稱如果自訂元素沒有與版位相符的項目,則會顯示 <slot> 的內容。 因此,我們納入了包含一般內容的 <legend>,只要使用者在 HTML 的 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 元素視為匿名的內嵌 可設定樣式的元素。與 <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>) 不包含在自訂元素中 因此不會顯示該區塊

我們定義名為 star-rating 的自訂元素 方法是擴充 HTMLElement

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。

注意到星星如何重新設定樣式的圓形按鈕?不屬於標準 DOM 而非標準 DOM 的一部分,因此不適用於 Codepen CSS 分頁中的樣式。該分頁的 CSS 樣式的範圍僅限於文件,而非陰影 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,且已隱藏 包括元件外的所有內容、轉譯的版位內容、<anyElement slot="star-rating-legend"> 部分 <star-rating> 並未封裝。

設定目前範圍外的樣式

在 shadow DOM 內為文件設定樣式,以及設定 shadow DOM 內容的樣式,但其實不是很簡單 套用全域樣式陰影界線 (即陰影 DOM 結束且一般 DOM 開始) 只能週遊 都是刻意開發的讀者

「陰影樹狀結構」是陰影 DOM 中的 DOM 樹狀結構。陰影根目錄是陰影樹狀結構的根節點。

:host 虛擬類別會選取 <star-rating>,即陰影主機元素。 陰影主機是指 shadow DOM 附加至這個 DOM 節點。如果只要指定特定主機版本,請使用 :host()。 這會只會選取與所傳遞參數相符的陰影主機元素,例如類別或屬性選取器。如何選取 所有自訂元素都可以在全域 CSS 中使用 star-rating { /* styles */ },或在範本樣式中使用 :host(:not(#nonExistantId))。簡單來說 全球 CSS 供應商勝出。

::slotted() 虛擬元素跨越陰影 DOM 邊界 在 shadow DOM 的範圍內如果與選取器相符,就會選取運算單元化的元素。在本範例中,::slotted(legend) 符合我們的三個圖例。

您必須先編輯範本,才能指定全域範圍內 CSS 的 Shad 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 的新概念。

隨堂測驗

測試你對範本、版位和陰影的瞭解程度。

根據預設,陰影 DOM 外的樣式會設定內部元素的樣式。

正確。
請再試一次。
不正確。
答對了!

下列何者是 <template> 元素的正確說明?

一種通用元素,可用於顯示網頁中的任何內容,
請再試一次。
預留位置元素。
請再試一次。
這個元素用於宣告 HTML 的片段 (預設為不轉譯)。
答對了!