<template>: 内容模板元素
Baseline 广泛可用 *
<template>
HTML 元素作为一种机制,用于保存 HTML 片段,这些片段可以通过 JavaScript 在以后使用,或者立即生成到 shadow DOM 中。
属性
此元素包含全局属性。
shadowrootmode
-
为父元素创建一个 shadow root。它是
Element.attachShadow()
方法的声明式版本,并接受相同的 枚举 值。注意:HTML 解析器会为具有此属性并设置为允许值的节点中的第一个
<template>
在 DOM 中创建一个ShadowRoot
对象。如果未设置此属性,或者未设置为允许值,或者已经在同一个父节点中声明式地创建了ShadowRoot
,则会构造一个HTMLTemplateElement
。解析后,HTMLTemplateElement
不能随后更改为 shadow root,例如通过设置HTMLTemplateElement.shadowRootMode
。注意:你可能会在旧教程和示例中发现非标准的
shadowroot
属性,该属性曾受 Chrome 90-110 支持。此属性已删除,并被标准shadowrootmode
属性取代。 shadowrootclonable
-
将使用此元素创建的
ShadowRoot
的clonable
属性值设置为true
。如果设置,使用Node.cloneNode()
或Document.importNode()
创建的 shadow host(此<template>
的父元素)的克隆将包含一个 shadow root。 shadowrootdelegatesfocus
-
将使用此元素创建的
ShadowRoot
的delegatesFocus
属性值设置为true
。如果设置此值,并且选中了 shadow tree 中不可聚焦的元素,则焦点会委托给 tree 中的第一个可聚焦元素。默认值为false
。 shadowrootserializable
-
将使用此元素创建的
ShadowRoot
的serializable
属性值设置为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
属性,其值为 open
或 closed
,HTML 解析器将立即生成一个 shadow DOM。该元素在 DOM 中被替换为其内容,这些内容被包装在 ShadowRoot
中,并附加到父元素。这等同于调用 Element.attachShadow()
将 shadow root 附加到元素。
如果元素对于 shadowrootmode
具有任何其他值,或者没有 shadowrootmode
属性,解析器将生成一个 HTMLTemplateElement
。同样,如果存在多个声明式 shadow root,则只有第一个会被 ShadowRoot
替换 — 后续实例将解析为 HTMLTemplateElement
对象。
示例
生成表格行
我们首先从示例的 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 将行插入表格中,每行都以模板为基础进行构造。
// 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
属性。
<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>
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
的元素设置为蓝色,并设置主机元素的正常样式。
<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
属性之外,其他都相同,该属性将焦点委托给树中的第一个可聚焦元素,如果选择了树中不可聚焦的元素。
<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>
元素获得焦点时应用红色边框。
div:focus {
border: 2px solid red;
}
结果如下所示。当 HTML 首次渲染时,元素没有样式,如第一张图片所示。对于未设置 shadowrootdelegatesfocus
的 shadow root,你可以在除了 <input>
之外的任何地方单击,焦点不会改变(如果选择 <input>
元素,它将看起来像第二张图片)。
对于设置了 shadowrootdelegatesfocus
的 shadow root,单击文本(不可聚焦)会选择 <input>
元素,因为这是树中第一个可聚焦的元素。这也会使父元素获得焦点,如下所示。
避免 DocumentFragment 陷阱
当传入 DocumentFragment
值时,Node.appendChild
和类似方法只会将该值的子节点移动到目标节点中。因此,通常最好将事件处理程序附加到 DocumentFragment
的子节点上,而不是附加到 DocumentFragment
本身。
考虑以下 HTML 和 JavaScript
HTML
<div id="container"></div>
<template id="template">
<div>Click me</div>
</template>
JavaScript
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
时事件处理程序会被复制,并且单击它会像预期一样工作。
技术摘要
规范
规范 |
---|
HTML # the-template-element |
浏览器兼容性
加载中…
另见
part
和exportparts
HTML 属性<slot>
HTML 元素:has-slotted
、:host
、:host()
和:host-context()
CSS 伪类::part
和::slotted
CSS 伪元素ShadowRoot
接口- 使用模板和插槽
- CSS 作用域模块
- 在《使用 Shadow DOM》中使用 HTML 声明式 Shadow DOM
- web.dev 上关于声明式 Shadow DOM 的文章 (2023)