HTML's 新增範本標記

標準化用戶端範本

簡介

範本的概念並不是網頁程式開發的概念。事實上,Django (Python)、ERB/Haml (Ruby) 和 Smarty (PHP) 等伺服器端範本語言/引擎已存在很久。不過,過去幾年來,我們看到 MVC 架構不斷爆炸。這些方法都略有不同,但在呈現展示層 (亦稱為「資料檢視」) 的常見機制:範本。

面對現實,範本非常出色。問問自己吧連定義都讓你感到溫馨又舒適:

「...不需要每次都重新建立...」雖然不認識您,但我喜歡避免多餘的處理工作。為何網路平台對開發人員顯然關心的事物缺乏原生支援?

WhatWG HTML 範本規格是解答。該定義會定義新的 <template> 元素,用於描述用戶端範本的標準 DOM 式方法。範本可讓您宣告標記片段片段,這些片段會剖析為 HTML、在網頁載入時不使用,但之後可以在執行階段進行例項化。引述:Rafael Weinstein

他們要在這裡放置大量 HTML 程式碼,不希望瀏覽器出於任何理由。

Rafael Weinstein (規格作者)

特徵偵測

如要偵測 <template>,請建立 DOM 元素,並檢查 .content 屬性是否存在:

function supportsTemplate() {
    return 'content' in document.createElement('template');
}

if (supportsTemplate()) {
    // Good to go!
} else {
    // Use old templating techniques or libraries.
}

宣告範本內容

HTML <template> 元素代表標記中的範本。其中包含「範本內容」,基本上就是插入可複製 DOM 的區塊。範本就像是鷹架元件,可以在應用程式的生命週期內使用 (並重複使用)。

如要建立範本內容,請宣告一些標記並納入 <template> 元素中:

<template id="mytemplate">
    <img src="" alt="great image">
    <div class="comment"></div>
</template>

重點特色

將內容納入 <template> 提供了幾項重要屬性。

  1. 內容在啟用前無法有效插入。基本上,標記為隱藏 DOM,且不會轉譯。

  2. 範本中的內容都不會產生副作用。指令碼無法執行、無法載入圖片、不會播放音訊,而是必須先使用範本。

  3. 系統不會將內容視為不在文件中。在主頁面中使用 document.getElementById()querySelector() 不會傳回範本的子節點。

  4. 範本可以放在 <head><body><frameset> 內的任何位置,且可包含這些元素中允許的任何類型的內容。請注意,「任何位置」代表 <template> 可以安全用於 HTML 剖析器不允許...全部、內容模型子項。它也可以做為 <table><select> 的子項:

<table>
  <tr>
    <template id="cells-to-repeat">
      <td>some content</td>
    </template>
  </tr>
</table>

啟用範本

如要使用某個範本,您需要啟用該範本。否則系統一律不會顯示其內容。最簡單的做法是使用 document.importNode() 建立其 .content 的深度副本。.content 屬性是內含範本 gut 的唯讀 DocumentFragment

var t = document.querySelector('#mytemplate');
// Populate the src at runtime.
t.content.querySelector('img').src = 'logo.png';

var clone = document.importNode(t.content, true);
document.body.appendChild(clone);

蓋完範本後,範本的內容就會「上線」。在這個特定範例中,系統會複製內容、建立圖片要求,並轉譯最終的標記。

試聽帶

範例:插入指令碼

這個範例會說明範本內容的傳播程度。只有在使用者按下按鈕時,將範本蓋過蓋章時,<script> 才會執行。

<button onclick="useIt()">Use me</button>
<div id="container"></div>
<script>
  function useIt() {
    var content = document.querySelector('template').content;
    // Update something in the template DOM.
    var span = content.querySelector('span');
    span.textContent = parseInt(span.textContent) + 1;
    document.querySelector('#container').appendChild(
      document.importNode(content, true)
    );
  }
</script>

<template>
  <div>Template used: <span>0</span></div>
  <script>alert('Thanks!')</script>
</template>

範例:使用範本建立 Shadow DOM

大多數使用者會將標記字串設為 .innerHTML,藉此將 Shadow DOM 附加至主機:

<div id="host"></div>
<script>
  var shadow = document.querySelector('#host').createShadowRoot();
  shadow.innerHTML = '<span>Host node</span>';
</script>

這種做法的問題是 Shadow DOM 取得越複雜,您使用的字串越多。也不會擴大規模 使遊戲混亂快速傳動此方法也是 XSS 最初的誕生方式!<template> 即可還原。

更嚴謹的做法是將範本內容附加至陰影根目錄,以便直接使用 DOM:

<template>
<style>
  :host {
    background: #f8f8f8;
    padding: 10px;
    transition: all 400ms ease-in-out;
    box-sizing: border-box;
    border-radius: 5px;
    width: 450px;
    max-width: 100%;
  }
  :host(:hover) {
    background: #ccc;
  }
  div {
    position: relative;
  }
  header {
    padding: 5px;
    border-bottom: 1px solid #aaa;
  }
  h3 {
    margin: 0 !important;
  }
  textarea {
    font-family: inherit;
    width: 100%;
    height: 100px;
    box-sizing: border-box;
    border: 1px solid #aaa;
  }
  footer {
    position: absolute;
    bottom: 10px;
    right: 5px;
  }
</style>
<div>
  <header>
    <h3>Add a Comment
  </header>
  <content select="p"></content>
  <textarea></textarea>
  <footer>
    <button>Post</button>
  </footer>
</div>
</template>

<div id="host">
  <p>Instructions go here</p>
</div>

<script>
  var shadow = document.querySelector('#host').createShadowRoot();
  shadow.appendChild(document.querySelector('template').content);
</script>

我瞭解了

我在公開使用 <template> 時可能會遇到以下問題:

  • 如果使用的是modpagespeed,請留意這個錯誤。定義內嵌 <style scoped> 的範本可透過 PageSpeed 的 CSS 重新編寫規則移至標題。
  • 無法「預先算繪」範本,也就是說,伺服器和用戶端無法預先載入資產、處理 JS、下載初始 CSS 等。範本只有在上線時才會顯示。
  • 請謹慎使用巢狀範本。但實際上的行為不如預期。例如:

    <template>
      <ul>
        <template>
          <li>Stuff</li>
        </template>
      </ul>
    </template>
    

    啟用外部範本並不會啟用內部範本。也就是說,巢狀範本需要手動啟用其子項。

邁向標準之路

讓我們記不得一下我們的是從哪裡來的。標準 HTML 範本問世以來都是很長的路多年來,我們不斷想出一些聰明的技巧 來建立可重複使用的範本以下是我常遇到的兩個常見情況。 我們在本文中列出了這些資料欄,以便進行比較。

方法 1:畫面外 DOM

長久以來,人們一直使用一種方法,就是建立「畫面外」DOM,並使用 hidden 屬性或 display:none 將其隱藏在檢視畫面中。

<div id="mytemplate" hidden>
  <img src="logo.png">
  <div class="comment"></div>
</div>

雖然這項技巧有效,但仍有一些缺點。這項技巧的總結:

  • 使用 DOM - 瀏覽器瞭解 DOM。你真的很擅長。我們可以輕鬆複製該網址。
  • 未顯示任何內容 - 加入 hidden 會導致區塊無法顯示。
  • Not inert - 即使內容處於隱藏狀態,系統仍會對圖片發出網路要求。
  • 簡易樣式和主題設定 - 嵌入頁面必須在所有 CSS 規則前面加上 #mytemplate 前置字串,才能將樣式範圍縮小至範本。這種狀況很脆弱,而且無法保證日後不會遇到命名衝突。例如,如果嵌入的網頁已有使用該 ID 的元素,我們就會改善該元素。

方法 2:超載指令碼

另一項技術就是超載 <script>,並以字串形式操控內容。John Resig 可能是 2008 年首度與他的 Micro Templating 公用程式分享這類資訊。現在還有很多新孩子,例如 handlebars.js 等街區。

例如:

<script id="mytemplate" type="text/x-handlebars-template">
  <img src="logo.png">
  <div class="comment"></div>
</script>

這項技巧的總結:

  • 未顯示任何內容 - 由於 <script> 預設為 display:none,瀏覽器不會轉譯這個區塊。
  • Inert - 瀏覽器不會將指令碼內容剖析為 JS,因為其類型設為「text/javascript」以外的內容。
  • 安全性問題:建議使用 .innerHTML。 在使用者提供的資料期間執行字串剖析,很容易導致 XSS 安全漏洞。

結論

還記得何時使用 jQuery 擺脫 DOM 的簡易操作嗎?結果是 querySelector()/querySelectorAll() 新增至平台。贏了,對吧?這個程式庫常用以使用 CSS 選取器和標準擷取 DOM,後來採用。不是每次都這樣有效,而是我

我認為 <template> 也是一樣。它可以標準化用戶端範本的方式,但更重要的是,它可省去 2008 個入侵的麻煩。在我的書籍中,讓整個網頁編寫程序變得更簡單、更易於維護,且更完整功能一直是一件好事。

其他資源