<template>: 内容模板元素

Baseline 广泛可用 *

此特性已稳定,并兼容多种设备和浏览器版本。自 ⁨2015 年 11 月⁩起,所有浏览器均已支持此特性。

* 此特性的某些部分可能存在不同级别的支持。

<template> HTML 元素作为一种机制,用于保存 HTML 片段,这些片段可以通过 JavaScript 在以后使用,或者立即生成到 shadow DOM 中。

属性

此元素包含全局属性

shadowrootmode

为父元素创建一个 shadow root。它是 Element.attachShadow() 方法的声明式版本,并接受相同的 枚举 值。

open

为 JavaScript 暴露内部 shadow root DOM(推荐用于大多数用例)。

closed

向 JavaScript 隐藏内部 shadow root DOM。

注意:HTML 解析器会为具有此属性并设置为允许值的节点中的第一个 <template> 在 DOM 中创建一个 ShadowRoot 对象。如果未设置此属性,或者未设置为允许值,或者已经在同一个父节点中声明式地创建了 ShadowRoot,则会构造一个 HTMLTemplateElement。解析后,HTMLTemplateElement 不能随后更改为 shadow root,例如通过设置 HTMLTemplateElement.shadowRootMode

注意:你可能会在旧教程和示例中发现非标准的 shadowroot 属性,该属性曾受 Chrome 90-110 支持。此属性已删除,并被标准 shadowrootmode 属性取代。

shadowrootclonable

将使用此元素创建的 ShadowRootclonable 属性值设置为 true。如果设置,使用 Node.cloneNode()Document.importNode() 创建的 shadow host(此 <template> 的父元素)的克隆将包含一个 shadow root。

shadowrootdelegatesfocus

将使用此元素创建的 ShadowRootdelegatesFocus 属性值设置为 true。如果设置此值,并且选中了 shadow tree 中不可聚焦的元素,则焦点会委托给 tree 中的第一个可聚焦元素。默认值为 false

shadowrootserializable

将使用此元素创建的 ShadowRootserializable 属性值设置为 true。如果设置,可以通过调用 Element.getHTML()ShadowRoot.getHTML() 方法并将 options.serializableShadowRoots 参数设置为 true 来序列化 shadow root。默认值为 false

用法说明

此元素没有允许的内容,因为 HTML 源中嵌套在其中的所有内容实际上都不会成为 <template> 元素的子级。<template> 元素的 Node.childNodes 属性始终为空,并且只能通过特殊的 content 属性访问这些嵌套内容。但是,如果在 <template> 元素上调用 Node.appendChild() 或类似方法,那么你将把子级插入到 <template> 元素本身中,这违反了其内容模型,并且实际上不会更新 content 属性返回的 DocumentFragment

由于 <template> 元素的解析方式,模板中所有 <html><head><body> 的开始和结束标签都是语法错误,并且会被解析器忽略,因此 <template><head><title>Test</title></head></template><template><title>Test</title></template> 相同。

使用 <template> 元素主要有两种方式。

模板文档片段

默认情况下,元素的 content 不会被渲染。相应的 HTMLTemplateElement 接口包含一个标准的 content 属性(没有等效的 content/markup 属性)。此 content 属性是只读的,并包含一个 DocumentFragment,其中包含模板所表示的 DOM 子树。此片段可以通过 cloneNode 方法克隆并插入到 DOM 中。

使用 content 属性时要小心,因为返回的 DocumentFragment 可能会出现意外行为。有关更多详细信息,请参阅下面的避免 DocumentFragment 陷阱部分。

声明式 Shadow DOM

如果 <template> 元素包含 shadowrootmode 属性,其值为 openclosed,HTML 解析器将立即生成一个 shadow DOM。该元素在 DOM 中被替换为其内容,这些内容被包装在 ShadowRoot 中,并附加到父元素。这等同于调用 Element.attachShadow() 将 shadow root 附加到元素。

如果元素对于 shadowrootmode 具有任何其他值,或者没有 shadowrootmode 属性,解析器将生成一个 HTMLTemplateElement。同样,如果存在多个声明式 shadow root,则只有第一个会被 ShadowRoot 替换 — 后续实例将解析为 HTMLTemplateElement 对象。

示例

生成表格行

我们首先从示例的 HTML 部分开始。

html
<table id="producttable">
  <thead>
    <tr>
      <td>UPC_Code</td>
      <td>Product_Name</td>
    </tr>
  </thead>
  <tbody>
    <!-- existing data could optionally be included here -->
  </tbody>
</table>

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

首先,我们有一个表格,稍后将使用 JavaScript 代码将内容插入其中。然后是模板,它描述了代表单个表格行的 HTML 片段的结构。

现在表格已创建,模板已定义,我们使用 JavaScript 将行插入表格中,每行都以模板为基础进行构造。

js
// Test to see if the browser supports the HTML template element by checking
// for the presence of the template element's content attribute.
if ("content" in document.createElement("template")) {
  // Instantiate the table with the existing HTML tbody
  // and the row with the template
  const tbody = document.querySelector("tbody");
  const template = document.querySelector("#productrow");

  // Clone the new row and insert it into the table
  const clone = template.content.cloneNode(true);
  let td = clone.querySelectorAll("td");
  td[0].textContent = "1235646565";
  td[1].textContent = "Stuff";

  tbody.appendChild(clone);

  // Clone the new row and insert it into the table
  const clone2 = template.content.cloneNode(true);
  td = clone2.querySelectorAll("td");
  td[0].textContent = "0384928528";
  td[1].textContent = "Acme Kidney Beans 2";

  tbody.appendChild(clone2);
} else {
  // Find another way to add the rows to the table because
  // the HTML template element is not supported.
}

结果是原始的 HTML 表格,并通过 JavaScript 追加了两行新行

实现声明式 Shadow DOM

在此示例中,标记的开头包含一个隐藏的支持警告。如果浏览器不支持 shadowrootmode 属性,则稍后将通过 JavaScript 设置此警告以显示。接下来,有两个 <article> 元素,每个都包含嵌套的 <style> 元素,具有不同的行为。第一个 <style> 元素对整个文档是全局的。第二个元素的作用域是为 <template> 元素生成的 shadow root,因为存在 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

此示例演示了 shadowrootdelegatesfocus 如何应用于声明式创建的 shadow root,以及它对焦点的影响。

该代码首先使用带有 shadowrootmode 属性的 <template> 元素在 <div> 元素内部声明一个 shadow root。这会显示一个包含文本的不可聚焦的 <div> 和一个可聚焦的 <input> 元素。它还使用 CSS 将带有 :focus 的元素设置为蓝色,并设置主机元素的正常样式。

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>

第二个代码块除了设置了 shadowrootdelegatesfocus 属性之外,其他都相同,该属性将焦点委托给树中的第一个可聚焦元素,如果选择了树中不可聚焦的元素。

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>

最后,我们使用以下 CSS 在父 <div> 元素获得焦点时应用红色边框。

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

结果如下所示。当 HTML 首次渲染时,元素没有样式,如第一张图片所示。对于未设置 shadowrootdelegatesfocus 的 shadow root,你可以在除了 <input> 之外的任何地方单击,焦点不会改变(如果选择 <input> 元素,它将看起来像第二张图片)。

Screenshot of code with no focus set

对于设置了 shadowrootdelegatesfocus 的 shadow root,单击文本(不可聚焦)会选择 <input> 元素,因为这是树中第一个可聚焦的元素。这也会使父元素获得焦点,如下所示。

Screenshot of the code where the element has focus

避免 DocumentFragment 陷阱

当传入 DocumentFragment 值时,Node.appendChild 和类似方法只会将该值的子节点移动到目标节点中。因此,通常最好将事件处理程序附加到 DocumentFragment 的子节点上,而不是附加到 DocumentFragment 本身。

考虑以下 HTML 和 JavaScript

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 = template.content.cloneNode(true);
firstClone.addEventListener("click", clickHandler);
container.appendChild(firstClone);

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

结果

由于 firstClone 是一个 DocumentFragment,因此在调用 appendChild 时,只有它的子节点被添加到 container 中;firstClone 的事件处理程序不会被复制。相反,因为事件处理程序被添加到 secondClone 的第一个子节点中,所以在调用 appendChild 时事件处理程序会被复制,并且单击它会像预期一样工作。

技术摘要

内容类别 元数据内容流内容短语内容脚本支持元素
允许内容 无(参阅使用说明
标签省略 无,起始标签和结束标签都必须存在。
允许父级 任何接受元数据内容短语内容脚本支持元素的元素。也允许作为不具有span属性的<colgroup>元素的子元素。
隐式 ARIA 角色 没有对应的角色
允许的 ARIA 角色 不允许 role
DOM 接口 HTMLTemplateElement

规范

规范
HTML
# the-template-element

浏览器兼容性

另见