[譯]編寫可以複用的 HTML 模板

Mirosalva發表於2019-04-04

編寫可以複用的 HTML 模板

在我們的上一篇文章中, 我們討論了 web 元件規範(自定義元素、shadow DOM 和 HTML 模板)的高階特性。在本文以及接下來的三篇文章中,我們將這些技術應用到測試並更詳細地去驗證它們,看下我們在如今的產品如何應用它們。為了做到這些,我們將會從零開始構建一個自定義模式的對話方塊,來檢視這些不同的技術如何組裝在一起。

系列文章:

  1. Web 元件簡介
  2. 編寫可以複用的 HTML 模板 (本文)
  3. 從 0 開始建立自定義元素
  4. 使用 Shadow DOM 封裝樣式和結構
  5. Web 元件的高階工具

HTML 模板

Web 元件規範中最不被認可但是最強大的功能之一是 <模板> 元素。在這個系列的第一篇文章中,我們將這種模板元素定義為『僅在呼叫時才渲染的使用者自定義 HTML 模板』。換句話說,模板就是一種當瀏覽器被告知時才執行的 HTML 程式碼,其他情況下是被忽略的。

這種模組可以通過許多有趣的方式去傳遞和應用。基於本文的目的,我們將看下如何為一種最終應用到自定義元素中的對話方塊建立模板。

定義你的模板

就像它聽起來這樣簡單,一個 <template> 是一種 HTML 元素,所以一個含內容的模板所具備的最基本形式如下:

<template>
  <h1>Hello world</h1>
</template>
複製程式碼

在瀏覽器中執行這段程式碼會顯示空白頁面,因為瀏覽器並沒有渲染模板元素內容。這種方式的強大之處在於它允許我們儲存自定義內容(或內容結構),以供後續使用,而不需要使用 JavaScript 來動態編寫 HTML 程式碼。

為了使用模板,我們 需要用到 JavaScript。

const template = document.querySelector('template');
const node = document.importNode(template.content, true);
document.body.appendChild(node);
複製程式碼

真正神奇的地方在於 document.importNode 方法。這個函式將會為模板的 內容 建立一份副本,並且做好將拷貝插入其他文件(或文件片段)的準備。函式的第一個引數獲取到模板的內容,第二個引數告訴瀏覽器要對元素的 DOM 子樹做一份深度拷貝(也就是拷貝它的所有子節點)。

我們可以直接使用 template.content,但是這樣做的話,我們隨後需要把內容從元素中移除並將它拼接到其他文件的 body 部分。任何 DOM 節點僅可以被接入到一個位置,所以隨後對模板內容的使用將會導致空文件片段(基本上是一個空值),因為之前已移動了內容物件。使用 document.importNode 允許我們在不同的位置來複用同一個模板內容的例項。

以上程式碼執行後,節點內容會被拼接到 document.body 物件,並被渲染顯示給使用者。這樣最終使我們能夠做許多有趣的事情,比如為我們的使用者(或者我們程式的消費者)提供建立內容的模板,類似下面的 demo,在第一篇文章我們討論過:

請參閱筆記模板樣例,來自 CodePen 的 Caleb Williams (@calebdwilliams)。

這個例子中,我們提供了兩個模板來渲染同樣的內容 —— 作者和他寫的書。當表格變化時,我們選擇渲染與該變化值相關聯的模板。使用相同的技術允許我們最終建立一個自定義元素,該元素將使用稍後定義的模板。

模板的多功能性

模板中一個有趣的點是我們可以包含 任意 HTML,包括指令碼和樣式元素。一個非常簡單的模板例子是新增一個可以提示被點選的按鈕。

<button id="click-me">Log click event</button>
複製程式碼

讓我們加點樣式:

button {
  all: unset;
  background: tomato;
  border: 0;
  border-radius: 4px;
  color: white;
  font-family: Helvetica;
  font-size: 1.5rem;
  padding: .5rem 1rem;
}
複製程式碼

...然後通過一個非常簡單的指令碼來呼叫按鈕:

const button = document.getElementById('click-me');
button.addEventListener('click', event => alert(event));
複製程式碼

當然,我們可以直接使用 <style></script> 標籤來將他們放在同一個檔案中,而非放在分離的檔案中:

<template id="template">
  <script>
    const button = document.getElementById('click-me');
    button.addEventListener('click', event => alert(event));
  </script>
  <style>
    #click-me {
      all: unset;
      background: tomato;
      border: 0;
      border-radius: 4px;
      color: white;
      font-family: Helvetica;
      font-size: 1.5rem;
      padding: .5rem 1rem;
    }
  </style>
  <button id="click-me">Log click event</button>
</template>
複製程式碼

一旦這個元素被加入到 DOM 結構中,我們會看到一個 ID 為 #click-me 的新按鈕,一個全域性的 CSS selector 被繫結到這個按鈕的 ID,一個簡單的事件監聽回撥函式會提示元素的點選事件。

至於我們的指令碼,我們僅需使用 document.importNode 來拼接內容,並且我們有一個包含大致內容的 HTML 模板,在頁與頁之間可以複用。

請參閱筆記包含指令碼和樣式的模板例子,來自 CodePen 的 Caleb Williams (@calebdwilliams)。

為我們的對話方塊編寫模板

回到我們編寫一個對話方塊元素這個任務,我們希望定義自己的模板內容和樣式。

<template id="one-dialog">
  <script>
    document.getElementById('launch-dialog').addEventListener('click', () => {
      const wrapper = document.querySelector('.wrapper');
      const closeButton = document.querySelector('button.close');
      const wasFocused = document.activeElement;
      wrapper.classList.add('open');
      closeButton.focus();
      closeButton.addEventListener('click', () => {
        wrapper.classList.remove('open');
        wasFocused.focus();
      });
    });
  </script>
  <style>
    .wrapper {
      opacity: 0;
      transition: visibility 0s, opacity 0.25s ease-in;
    }
    .wrapper:not(.open) {
      visibility: hidden;
    }
    .wrapper.open {
      align-items: center;
      display: flex;
      justify-content: center;
      height: 100vh;
      position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
      opacity: 1;
      visibility: visible;
    }
    .overlay {
      background: rgba(0, 0, 0, 0.8);
      height: 100%;
      position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
      width: 100%;
    }
    .dialog {
      background: #ffffff;
      max-width: 600px;
      padding: 1rem;
      position: fixed;
    }
    button {
      all: unset;
      cursor: pointer;
      font-size: 1.25rem;
      position: absolute;
        top: 1rem;
        right: 1rem;
    }
    button:focus {
      border: 2px solid blue;
    }
  </style>
  <div class="wrapper">
  <div class="overlay"></div>
    <div class="dialog" role="dialog" aria-labelledby="title" aria-describedby="content">
      <button class="close" aria-label="Close">&#x2716;&#xfe0f;</button>
      <h1 id="title">Hello world</h1>
      <div id="content" class="content">
        <p>This is content in the body of our modal</p>
      </div>
    </div>
  </div>
</template>
複製程式碼

這段程式碼將成為我們對話方塊的基礎部分。簡單介紹一下,我們有一個全域性的關閉按鈕,一個標題和一些內容。我們也新增了一些行為來實現視覺化觸發對話方塊(儘管它還無法被訪問)。不幸的是,樣式和指令碼內容並非僅限作用於我們的模板,而是應用於整個檔案,當我們將多個模板例項新增到 DOM 時,並沒有產生理想的中的效果。在下篇文章中,我們將應用自定義元素f生成方法並建立我們自己的元素,實時使用該模板並封裝元素的行為。

請查閱筆記以指令碼模板來編寫對話方塊 ,來自 CodePen 的 Caleb Williams (@calebdwilliams)。

Article Series:

  1. Web Components 簡介
  2. 編寫可以複用的 HTML 模板 (本文)
  3. 從 0 開始建立自定義元素
  4. 使用 Shadow DOM 封裝樣式和結構
  5. Web 元件的高階工具

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章