<template>: Phần tử template nội dung

Baseline Widely available *

This feature is well established and works across many devices and browser versions. It’s been available across browsers since November 2015.

* Some parts of this feature may have varying levels of support.

Phần tử <template> trong HTML đóng vai trò là cơ chế để giữ các đoạn HTML, có thể được dùng sau qua JavaScript hoặc được tạo ngay lập tức vào shadow DOM.

Thuộc tính

Phần tử này bao gồm các thuộc tính toàn cục.

shadowrootmode

Tạo shadow root cho phần tử cha. Đây là phiên bản khai báo của phương thức Element.attachShadow() và chấp nhận các giá trị enumerated giống nhau.

open

Hiển thị shadow root DOM nội bộ cho JavaScript (được khuyến nghị cho hầu hết các trường hợp sử dụng).

closed

Ẩn shadow root DOM nội bộ khỏi JavaScript.

Note: Trình phân tích cú pháp HTML tạo đối tượng ShadowRoot trong DOM cho <template> đầu tiên trong một node với thuộc tính này được đặt thành giá trị được phép. Nếu thuộc tính không được đặt, hoặc không được đặt thành giá trị được phép — hoặc nếu ShadowRoot đã được tạo khai báo trong cùng một cha — thì một HTMLTemplateElement được tạo ra. Một HTMLTemplateElement không thể được thay đổi thành shadow root sau khi phân tích cú pháp, ví dụ bằng cách đặt HTMLTemplateElement.shadowRootMode.

Note: Bạn có thể tìm thấy thuộc tính không chuẩn shadowroot trong các hướng dẫn và ví dụ cũ hơn trước đây được hỗ trợ trong Chrome 90-110. Thuộc tính này đã bị xóa và được thay thế bằng thuộc tính tiêu chuẩn shadowrootmode.

shadowrootclonable

Đặt giá trị của thuộc tính clonable của ShadowRoot được tạo bằng phần tử này thành true. Nếu được đặt, bản sao của shadow host (phần tử cha của <template> này) được tạo bằng Node.cloneNode() hoặc Document.importNode() sẽ bao gồm shadow root trong bản sao.

shadowrootcustomelementregistry

Đặt thuộc tính customElementRegistry của ShadowRoot được tạo bằng phần tử này thành null, thay vì registry custom element của tài liệu. Điều này cho phép CustomElementRegistry có phạm vi được gắn sau bằng CustomElementRegistry.initialize().

shadowrootdelegatesfocus

Đặt giá trị của thuộc tính delegatesFocus của ShadowRoot được tạo bằng phần tử này thành true. Nếu điều này được đặt và một phần tử không thể lấy tiêu điểm trong cây shadow được chọn, thì tiêu điểm sẽ được ủy quyền cho phần tử đầu tiên có thể lấy tiêu điểm trong cây. Giá trị mặc định là false.

shadowrootreferencetarget Experimental Non-standard

Đặt giá trị của thuộc tính referenceTarget của ShadowRoot được tạo bằng phần tử này. Giá trị phải là ID của một phần tử bên trong shadow DOM. Nếu được đặt, các tham chiếu mục tiêu đến phần tử host từ bên ngoài shadow DOM sẽ khiến phần tử mục tiêu được tham chiếu trở thành mục tiêu hiệu lực của tham chiếu đến phần tử host.

shadowrootserializable

Đặt giá trị của thuộc tính serializable của ShadowRoot được tạo bằng phần tử này thành true. Nếu được đặt, shadow root có thể được tuần tự hóa bằng cách gọi các phương thức Element.getHTML() hoặc ShadowRoot.getHTML() với tham số options.serializableShadowRoots được đặt là true. Giá trị mặc định là false.

Ghi chú sử dụng

Phần tử này không có nội dung được phép, vì mọi thứ được lồng bên trong nó trong nguồn HTML thực tế không trở thành con của phần tử <template>. Thuộc tính Node.childNodes của phần tử <template> luôn trống, và bạn chỉ có thể truy cập nội dung được lồng đó qua thuộc tính đặc biệt content. Tuy nhiên, nếu bạn gọi Node.appendChild() hoặc các phương thức tương tự trên phần tử <template>, thì bạn sẽ chèn các con vào chính phần tử <template>, đây là vi phạm mô hình nội dung của nó và không thực sự cập nhật DocumentFragment được trả về bởi thuộc tính content.

Do cách phần tử <template> được phân tích cú pháp, tất cả các thẻ mở và đóng <html>, <head>, và <body> bên trong template là lỗi cú pháp và bị trình phân tích cú pháp bỏ qua, vì vậy <template><head><title>Test</title></head></template> giống như <template><title>Test</title></template>.

Có hai cách chính để dùng phần tử <template>.

Đoạn tài liệu template

Mặc định, nội dung của phần tử không được hiển thị. Giao diện HTMLTemplateElement tương ứng bao gồm thuộc tính content tiêu chuẩn (không có thuộc tính nội dung/đánh dấu tương đương). Thuộc tính content này là chỉ đọc và giữ DocumentFragment chứa cây con DOM được biểu diễn bởi template.

Các phương thức Node.cloneNode()Document.importNode() đều tạo bản sao của một node. Sự khác biệt là importNode() sao chép node trong ngữ cảnh của tài liệu gọi, trong khi cloneNode() dùng tài liệu của node đang được sao chép. Ngữ cảnh tài liệu xác định CustomElementRegistry để xây dựng bất kỳ custom element nào. Vì lý do này, hãy dùng document.importNode() để sao chép đoạn content để các hậu duệ custom element được xây dựng bằng các định nghĩa trong tài liệu hiện tại, thay vì tài liệu riêng biệt sở hữu nội dung template. Xem ví dụ trên trang Node.cloneNode() để biết thêm chi tiết.

Lưu ý rằng bản thân container DocumentFragment không nên có dữ liệu đính kèm vào nó. Xem ví dụ Dữ liệu trên DocumentFragment không được sao chép để biết thêm chi tiết.

Shadow DOM khai báo

Nếu phần tử <template> chứa thuộc tính shadowrootmode với giá trị open hoặc closed, trình phân tích cú pháp HTML sẽ tạo ngay lập tức shadow DOM. Phần tử được thay thế trong DOM bởi nội dung của nó được bọc trong ShadowRoot, được gắn vào phần tử cha. Đây là tương đương khai báo của việc gọi Element.attachShadow() để gắn shadow root vào phần tử.

Nếu phần tử có bất kỳ giá trị nào khác cho shadowrootmode, hoặc không có thuộc tính shadowrootmode, trình phân tích cú pháp tạo HTMLTemplateElement. Tương tự, nếu có nhiều shadow root khai báo, chỉ cái đầu tiên được thay thế bởi ShadowRoot — các phiên bản tiếp theo được phân tích cú pháp thành các đối tượng HTMLTemplateElement.

Ví dụ

Tạo hàng bảng

Đầu tiên chúng ta bắt đầu với phần HTML của ví dụ.

html
<table id="producttable">
  <thead>
    <tr>
      <td>UPC_Code</td>
      <td>Product_Name</td>
    </tr>
  </thead>
  <tbody>
    <!-- dữ liệu hiện có có thể tùy chọn được bao gồm ở đây -->
  </tbody>
</table>

<template id="productrow">
  <tr>
    <td class="record"></td>
    <td></td>
  </tr>
</template>

Đầu tiên, chúng ta có bảng mà chúng ta sau đó sẽ chèn nội dung bằng mã JavaScript. Sau đó là template, mô tả cấu trúc của đoạn HTML biểu diễn một hàng bảng duy nhất.

Bây giờ bảng đã được tạo và template được xác định, chúng ta dùng JavaScript để chèn các hàng vào bảng, mỗi hàng được xây dựng bằng template làm cơ sở.

js
// Kiểm tra xem trình duyệt có hỗ trợ phần tử HTML template bằng cách kiểm tra
// sự hiện diện của thuộc tính content của phần tử template.
if ("content" in document.createElement("template")) {
  // Khởi tạo bảng với HTML tbody hiện có
  // và hàng với template
  const tbody = document.querySelector("tbody");
  const template = document.querySelector("#productrow");

  // Sao chép hàng mới và chèn vào bảng
  const clone = document.importNode(template.content, true);
  let td = clone.querySelectorAll("td");
  td[0].textContent = "1235646565";
  td[1].textContent = "Stuff";

  tbody.appendChild(clone);

  // Sao chép hàng mới và chèn vào bảng
  const clone2 = document.importNode(template.content, true);
  td = clone2.querySelectorAll("td");
  td[0].textContent = "0384928528";
  td[1].textContent = "Acme Kidney Beans 2";

  tbody.appendChild(clone2);
} else {
  // Tìm cách khác để thêm hàng vào bảng vì
  // phần tử HTML template không được hỗ trợ.
}

Kết quả là bảng HTML gốc, với hai hàng mới được thêm vào qua JavaScript:

Triển khai shadow DOM khai báo

Trong ví dụ này, cảnh báo hỗ trợ ẩn được bao gồm ở đầu đánh dấu. Cảnh báo này sau được đặt để hiển thị qua JavaScript nếu trình duyệt không hỗ trợ thuộc tính shadowrootmode. Tiếp theo, có hai phần tử <article>, mỗi phần tử chứa các phần tử <style> lồng nhau với các hành vi khác nhau. Phần tử <style> đầu tiên là toàn cục cho toàn bộ tài liệu. Phần tử thứ hai được giới hạn trong shadow root được tạo ra thay cho phần tử <template> do sự hiện diện của thuộc tính shadowrootmode.

html
<p hidden>
  ⛔ Your browser doesn't support <code>shadowrootmode</code> attribute yet.
</p>
<article>
  <style>
    p {
      padding: 8px;
      background-color: wheat;
    }
  </style>
  <p>I'm in the DOM.</p>
</article>
<article>
  <template shadowrootmode="open">
    <style>
      p {
        padding: 8px;
        background-color: plum;
      }
    </style>
    <p>I'm in the shadow DOM.</p>
  </template>
</article>
js
const isShadowRootModeSupported = Object.hasOwn(
  HTMLTemplateElement.prototype,
  "shadowRootMode",
);

document
  .querySelector("p[hidden]")
  .toggleAttribute("hidden", isShadowRootModeSupported);

Shadow DOM khai báo với ủy quyền tiêu điểm

Ví dụ này minh họa cách shadowrootdelegatesfocus được áp dụng cho shadow root được tạo khai báo, và ảnh hưởng của nó đến tiêu điểm.

Mã đầu tiên khai báo shadow root bên trong phần tử <div>, bằng cách dùng phần tử <template> với thuộc tính shadowrootmode. Điều này hiển thị cả <div> không thể lấy tiêu điểm chứa văn bản và phần tử <input> có thể lấy tiêu điểm. Nó cũng dùng CSS để tạo kiểu các phần tử với :focus thành màu xanh, và đặt kiểu thông thường của phần tử host.

html
<div>
  <template shadowrootmode="open">
    <style>
      :host {
        display: block;
        border: 1px dotted black;
        padding: 10px;
        margin: 10px;
      }
      :focus {
        outline: 2px solid blue;
      }
    </style>
    <div>Clickable Shadow DOM text</div>
    <input type="text" placeholder="Input inside Shadow DOM" />
  </template>
</div>

Khối mã thứ hai giống hệt ngoại trừ việc nó đặt thuộc tính shadowrootdelegatesfocus, ủy quyền tiêu điểm cho phần tử đầu tiên có thể lấy tiêu điểm trong cây nếu một phần tử không thể lấy tiêu điểm trong cây được chọn.

html
<div>
  <template shadowrootmode="open" shadowrootdelegatesfocus>
    <style>
      :host {
        display: block;
        border: 1px dotted black;
        padding: 10px;
        margin: 10px;
      }
      :focus {
        outline: 2px solid blue;
      }
    </style>
    <div>Clickable Shadow DOM text</div>
    <input type="text" placeholder="Input inside Shadow DOM" />
  </template>
</div>

Cuối cùng chúng ta dùng CSS sau để áp dụng viền đỏ cho phần tử <div> cha khi nó có tiêu điểm.

css
div:focus {
  border: 2px solid red;
}

Kết quả được hiển thị bên dưới. Khi HTML lần đầu được hiển thị, các phần tử không có kiểu, như được hiển thị trong hình ảnh đầu tiên. Đối với shadow root không có shadowrootdelegatesfocus được đặt, bạn có thể nhấp vào bất cứ đâu ngoại trừ <input> và tiêu điểm không thay đổi (nếu bạn chọn phần tử <input> nó sẽ trông như hình ảnh thứ hai).

Screenshot of code with no focus set

Đối với shadow root với shadowrootdelegatesfocus được đặt, nhấp vào văn bản (không thể lấy tiêu điểm) sẽ chọn phần tử <input>, vì đây là phần tử đầu tiên có thể lấy tiêu điểm trong cây. Điều này cũng đặt tiêu điểm cho phần tử cha như được hiển thị bên dưới.

Screenshot of the code where the element has focus

Dữ liệu trên DocumentFragment không được sao chép

Khi giá trị DocumentFragment được truyền, Node.appendChild và các phương thức tương tự chỉ di chuyển các node con của giá trị đó vào node đích. Do đó, thường nên đính kèm trình xử lý sự kiện vào các con của DocumentFragment, thay vì chính DocumentFragment.

Hãy xem xét HTML và JavaScript sau:

HTML

html
<div id="container"></div>

<template id="template">
  <div>Click me</div>
</template>

JavaScript

js
const container = document.getElementById("container");
const template = document.getElementById("template");

function clickHandler(event) {
  event.target.append(" — Clicked this div");
}

const firstClone = document.importNode(template.content, true);
firstClone.addEventListener("click", clickHandler);
container.appendChild(firstClone);

const secondClone = document.importNode(template.content, true);
secondClone.children[0].addEventListener("click", clickHandler);
container.appendChild(secondClone);

Kết quả

firstCloneDocumentFragment, chỉ có các con của nó được thêm vào container khi appendChild được gọi; trình xử lý sự kiện của firstClone không được sao chép. Ngược lại, vì trình xử lý sự kiện được thêm vào node con đầu tiên của secondClone, trình xử lý sự kiện được sao chép khi appendChild được gọi, và nhấp vào nó hoạt động như mong đợi.

Tóm tắt kỹ thuật

Danh mục nội dung Nội dung metadata, nội dung luồng, nội dung diễn đạt, phần tử hỗ trợ script
Nội dung được phép Không có (xem Ghi chú sử dụng)
Bỏ thẻ Không, cả thẻ mở và thẻ đóng đều bắt buộc.
Phần tử cha được phép Bất kỳ phần tử nào chấp nhận nội dung metadata, nội dung diễn đạt, hoặc phần tử hỗ trợ script. Cũng được phép là con của phần tử <colgroup>không có thuộc tính span.
Vai trò ARIA ngầm định Không có vai trò tương ứng
Vai trò ARIA được phép Không có role nào được phép
Giao diện DOM HTMLTemplateElement

Đặc tả

Specification
HTML
# the-template-element

Tương thích trình duyệt

Xem thêm