网络组件的优势在于可重用性:您只需创建一次界面 widget,便可多次重复使用。在您 需要使用 JavaScript 来创建网络组件,则不需要 JavaScript 库。HTML 和关联的 API 可满足您所需的一切。
网络组件标准由三部分组成:HTML 模板、 自定义元素和阴影 DOM。 通过结合使用,它们可以构建自定义、独立(封装)、可重复使用的元素,这些元素可以无缝集成 就像我们已介绍过的所有其他 HTML 元素一样。
在本部分中,我们将创建 <star-rating>
元素,这是一个 Web 组件,可让用户对网站上的体验进行评分
评分范围为 1 星到 5 星为自定义元素命名时,建议使用全小写字母。此外,还要添加短划线
因为这有助于区分常规元素和自定义元素。
我们将讨论如何使用 <template>
和 <slot>
元素、slot
属性以及 JavaScript 来创建包含
封装的 Shadow DOM。然后,我们将重复使用已定义的元素,自定义一部分文本,
就像创建任何元素或网络组件一样。我们还将简要讨论在自定义元素内部和外部使用 CSS。
<template>
元素
<template>
元素用于声明使用 JavaScript 克隆和插入 DOM 的 HTML 片段。默认情况下,系统不会呈现元素的内容。而是使用 JavaScript 对其进行实例化。
<template id="star-rating-template">
<form>
<fieldset>
<legend>Rate your experience:</legend>
<rating>
<input type="radio" name="rating" value="1" aria-label="1 star" required />
<input type="radio" name="rating" value="2" aria-label="2 stars" />
<input type="radio" name="rating" value="3" aria-label="3 stars" />
<input type="radio" name="rating" value="4" aria-label="4 stars" />
<input type="radio" name="rating" value="5" aria-label="5 stars" />
</rating>
</fieldset>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</form>
</template>
由于 <template>
元素的内容不会写入屏幕,因此不会呈现 <form>
及其内容。
是的,此 Codepen 为空,但如果您检查 HTML 标签页,则会看到 <template>
标记。
在此示例中,<form>
不是 DOM 中 <template>
的子项。相反,<template>
元素的内容是子元素,
(由 HTMLTemplateElement.content
返回的 DocumentFragment
)
属性。要使其可见,必须使用 JavaScript 抓取内容并将这些内容附加到 DOM。
这段简短的 JavaScript 并未创建自定义元素。相反,此示例将 <template>
的内容附加到了 <body>
中。
内容已成为可见、可设置样式的 DOM 的一部分。
通过 JavaScript 实现仅针对 1 星评分的模板不是很有用,但为 可自定义星级微件非常有用。
<slot>
元素
我们添加一个槽,以包含自定义的每个发生实例图例。HTML 提供了一个 <slot>
元素作为占位符在 <template>
中,如果提供了名称,则会创建一个“已命名的槽位”。命名的槽位可以
来自定义网络组件中的内容。<slot>
元素为我们控制自定义子元素的位置
元素应插入其影子树中。
在我们的模板中,我们将 <legend>
更改为 <slot>
:
<template id="star-rating-template">
<form>
<fieldset>
<slot name="star-rating-legend">
<legend>Rate your experience:</legend>
</slot>
如果其他元素具有 slot 属性值与name
指定广告位的名称如果自定义元素没有与槽位匹配的元素,系统会呈现 <slot>
的内容。
因此,我们添加了一个包含通用内容的 <legend>
,如果任何人在其 HTML 中直接添加了不含任何内容的 <star-rating></star-rating>
,就可以呈现这些内容。
<star-rating>
<legend slot="star-rating-legend">Blendan Smooth</legend>
</star-rating>
<star-rating>
<legend slot="star-rating-legend">Hoover Sukhdeep</legend>
</star-rating>
<star-rating>
<legend slot="star-rating-legend">Toasty McToastface</legend>
<p>Is this text visible?</p>
</star-rating>
slot 属性是一个全局属性,用于
替换 <template>
中 <slot>
的内容。在我们的自定义元素中,具有 slot 属性的元素
是 <legend>
。其实不必如此。在我们的模板中,<slot name="star-rating-legend">
将替换为 <anyElement slot="star-rating-legend">
,
其中 <anyElement>
可以是任意元素,甚至可以是另一个自定义元素。
未定义的元素
在 <template>
中,我们使用了 <rating>
元素。这不是自定义元素。相反,它是未知元素。浏览器
在无法识别某个元素时不会失败。浏览器将无法识别的 HTML 元素视为匿名内嵌元素
可使用 CSS 设置样式的元素。与 <span>
类似,<rating>
和 <star-rating>
元素未应用用户代理
样式或语义。
请注意,系统不会呈现 <template>
和内容。<template>
是一个已知元素,其中包含
不会呈现。<star-rating>
元素尚未定义。在我们定义元素之前,浏览器会显示该元素
所有无法识别的元素。目前,无法识别的 <star-rating>
被视为匿名内嵌元素,因此其内容
(包括图例)以及第三个 <star-rating>
中的 <p>
,其显示方式与在 <span>
中的显示效果一致。
定义我们的元素,将这个无法识别的元素转换为自定义元素。
自定义元素
定义自定义元素需要使用 JavaScript。定义后,<star-rating>
元素的内容将替换为
影子根,其中包含我们与该模板相关联的模板的所有内容。模板中的 <slot>
元素已被替换
包含 <star-rating>
中其 slot
属性值与 <slot>
的名称值匹配的元素的内容,如果
有一个如果没有,则会显示模板的槽内容。
与槽位无关的自定义元素中的内容(即第三个 <star-rating>
中的 <p>Is this text visible?</p>
)不会包含在
影子根,因此不会显示。
我们定义自定义元素,并将其命名为 star-rating
。
通过扩展 HTMLElement
:
customElements.define('star-rating',
class extends HTMLElement {
constructor() {
super(); // Always call super first in constructor
const starRating = document.getElementById('star-rating-template').content;
const shadowRoot = this.attachShadow({
mode: 'open'
});
shadowRoot.appendChild(starRating.cloneNode(true));
}
});
现在,该元素已定义,每当浏览器遇到 <star-rating>
元素时,它都会按照定义的方式进行渲染
包含 #star-rating-template
(即我们的模板)的元素。浏览器会将一个 shadow DOM 树附加到该节点,将
将模板内容的克隆到该 shadow DOM。
请注意,您可以对哪些元素进行 attachShadow()
限制。
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(starRating.cloneNode(true));
查看开发者工具会发现,<template>
中的 <form>
是每个自定义元素的影子根的一部分。
在开发者工具中的每个自定义元素中,<template>
内容的克隆很明显,并且可以在浏览器中显示,但内容
自定义元素本身的某些部分未渲染到屏幕上。
在 <template>
示例中,我们将模板内容附加到文档正文,将内容添加到常规 DOM。
在 customElements
定义中,我们使用了相同的
appendChild()
,但克隆的模板内容附加到
封装的 shadow DOM。
注意到星形图标是如何变回未设置样式的单选按钮的了吗?由于作为 shadow DOM 的一部分而非标准 DOM,因此 Codepen 的 CSS 标签页中的样式设置不适用。该标签页的 CSS 样式的作用域限定为文档,而不是 shadow DOM,因此样式不会应用。我们必须创建 设置封装 Shadow DOM 内容的样式。
阴影 DOM
Shadow DOM 将 CSS 样式的范围限定为每个影子树,将其与文档的其余部分隔离开来。这意味着外部 CSS 也不会应用于组件,且组件样式也不会影响文档的其余部分,除非我们有意 将他们引导至
由于我们已将内容附加到了 shadow DOM,因此我们可以添加一个 <style>
元素
为自定义元素提供封装的 CSS。
由于将作用域限定为自定义元素,因此我们无需担心样式渗漏到文档的其余部分。我们可以
大大降低选择器的特异性。例如,由于自定义元素中使用的唯一输入是单选
按钮,可以使用 input
而不是 input[type="radio"]
作为选择器。
<template id="star-rating-template">
<style>
rating {
display: inline-flex;
}
input {
appearance: none;
margin: 0;
box-shadow: none;
}
input::after {
content: '\2605'; /* solid star */
font-size: 32px;
}
rating:hover input:invalid::after,
rating:focus-within input:invalid::after {
color: #888;
}
input:invalid::after,
rating:hover input:hover ~ input:invalid::after,
input:focus ~ input:invalid::after {
color: #ddd;
}
input:valid {
color: orange;
}
input:checked ~ input:not(:checked)::after {
color: #ccc;
content: '\2606'; /* hollow star */
}
</style>
<form>
<fieldset>
<slot name="star-rating-legend">
<legend>Rate your experience:</legend>
</slot>
<rating>
<input type="radio" name="rating" value="1" aria-label="1 star" required/>
<input type="radio" name="rating" value="2" aria-label="2 stars"/>
<input type="radio" name="rating" value="3" aria-label="3 stars"/>
<input type="radio" name="rating" value="4" aria-label="4 stars"/>
<input type="radio" name="rating" value="5" aria-label="5 stars"/>
</rating>
</fieldset>
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</form>
</template>
虽然 Web 组件使用 in-<template>
标记进行封装,但 CSS 样式的作用域限定为 shadow DOM 并处于隐藏状态
组件之外的所有内容、所渲染的槽内容、<anyElement slot="star-rating-legend">
的 <star-rating>
部分未封装。
样式设置在当前范围之外
您可以在 shadow DOM 中对文档进行样式设置,在 shadow DOM 中通过 全局样式可以遍历影子边界,即 shadow DOM 结束和常规 DOM 开始的位置,但只能遍历 。
影子树是 shadow DOM 内部的 DOM 树。影子根是影子树的根节点。
:host
伪类选择 <star-rating>
,即影子宿主元素。
影子主机是 shadow DOM 附加的 DOM 节点。如需仅定位到主机的特定版本,请使用 :host()
。
这样将仅选择与传递的参数匹配的影子宿主元素,例如类或属性选择器。选择
所有自定义元素,您都可以在全局 CSS 中使用 star-rating { /* styles */ }
,或在模板样式中使用 :host(:not(#nonExistantId))
。术语
则全局 CSS 将胜出。
::slotted()
伪元素跨越了 shadow DOM 边界
。如果与选择器匹配,它会选择带槽的元素。在我们的示例中,::slotted(legend)
与我们的三个图例相匹配。
要在全局范围内通过 CSS 定位到 shadow DOM,需要修改模板。part
属性可以添加到您要设置样式的任何元素中。然后使用 ::part()
伪元素
来匹配影子树中与传递的参数匹配的元素。伪元素的锚点或原始元素为
宿主或自定义元素名称,在本示例中为 star-rating
。该参数是 part
属性的值。
如果模板标记的开头是这样的:
<template id="star-rating-template">
<form part="formPart">
<fieldset part="fieldsetPart">
我们可以使用以下命令定位 <form>
和 <fieldset>
:
star-rating::part(formPart) { /* styles */ }
star-rating::part(fieldsetPart) { /* styles */ }
部件名称的作用与类类似:一个元素可以具有多个以空格分隔的部件名称,多个元素可以 都具有相同的部件名称。
Google 针对创建自定义元素提供了实用的核对清单。您可能还想了解 声明式 shadow DOM。
检查您的理解情况
测试您对模板、槽位和阴影的掌握情况。
默认情况下,来自 shadow DOM 外部的样式将为其中的元素设置样式。
以下哪一项是对 <template>
元素的正确说明?