今天,在翻译了17篇专门介绍与JavaScript相关联的所有功能的材料时,我们将讨论Web组件和旨在与之合作的各种标准。 特别要注意Shadow DOM技术。

复习
Web组件是旨在描述适用于重用的新DOM元素的API系列。 这些元素的功能与其余代码分开;它们可以在我们自己设计的Web应用程序中使用。
与Web组件相关的技术有四种:
- 影子DOM(影子DOM)
- HTML模板(HTML模板)
- 自定义元素
- HTML导入(HTML导入)
在本文中,我们将讨论Shadow DOM技术,该技术旨在创建基于组件的应用程序。 它提供了解决您可能已经遇到的常见Web开发问题的方法:
- DOM隔离:组件具有隔离的DOM树(这意味着
document.querySelector()
命令将不允许访问组件的影子DOM中的节点)。 另外,由于DOM组件是隔离的,因此它简化了Web应用程序中的CSS选择器系统,这使开发人员可以在不同的组件中使用相同的通用标识符和类名,而不必担心可能发生名称冲突。 - CSS隔离:在影子DOM中描述的CSS规则仅限于此。 这些样式不会离开元素,也不会与其他页面样式混合。
- 组成:为基于标记的组件开发声明性API。
Shadow DOM技术
假定您已经熟悉DOM和相关API的概念。 如果不是这样,您可以阅读
此材料。
Shadow DOM与常规DOM基本相同,但有两个区别:
- 首先是Shadow DOM的创建和使用方式,尤其是有关Shadow DOM与页面其余部分的关系。
- 第二个是Shadow DOM与页面有关的行为。
使用DOM时,将创建DOM节点,这些节点作为子级连接到页面的其他元素。 在Shadow DOM技术的情况下,创建了一个隔离的DOM树,该树连接了该元素,但与它的普通子元素分离。
这个孤立的子树称为影子树。 这样的树所附加的元素称为影子主机。 事实证明,添加到影子DOM子树的所有内容都是其所附着元素的本地元素,包括使用
<style>
标记描述的
<style>
。 这就是通过Shadow DOM技术提供CSS隔离的方式。
创建一个影子DOM
影子根是附加到宿主元素的文档的一部分。 将影子根元素附加到该元素后,该元素将获取影子DOM。 为了为某个元素创建影子DOM,您需要使用
element.attachShadow()
形式的命令:
var header = document.createElement('header'); var shadowRoot = header.attachShadow({mode: 'open'}); shadowRoot.appendChild(document.createElement('<p> Shadow DOM </p>');
应该注意的是,
在 Shadow DOM
规范中 ,列出了DOM影子子树无法连接的元素列表。
Shadow DOM中的合成
组合是Shadow DOM的最重要功能之一,它是创建Web应用程序的一种方法,该方法用于编写HTML代码。 在此过程中,程序员将组成页面的各种构建块(元素)组合在一起,并在必要时将它们相互嵌套。 例如,这些元素是诸如
<div>
,
<header>
,
<form>
类的元素,以及用于创建Web应用程序界面的其他元素,包括那些充当其他元素的容器的元素。
组合确定元素的功能,例如
<select>
,
<form>
和
<video>
,以包括其他HTML元素作为子元素,以及组织由不同元素组成的此类结构的特殊行为的能力。
例如,
<select>
元素具有用于以下拉列表的形式呈现
<option>
元素的装置,该下拉列表具有该列表的元素的预定内容。
考虑组成元素时使用的Shadow DOM的某些功能。
轻度dom
轻型DOM是由组件用户创建的标记。 此DOM在组件的影子DOM之外,并且是组件的子代。 想象一下,您创建了一个名为
<better-button>
的自定义组件,该组件扩展了标准HTML
<button>
元素的功能,并且用户需要向该新元素添加图像和一些文本。 看起来是这样的:
<extended-button> <img align="center" src="boot.png" slot="image"> <span>Launch</span> </extended-button>
<extended-button>
元素是程序员自己描述的自定义组件,该组件内的HTML代码是其Light DOM-该组件的用户向其添加的内容。
本示例中的影子DOM是
<extended-button>
组件。 这是组件的本地对象模型,它描述了与CSS外部世界隔离的内部结构,并封装了组件的实现细节。
扁平化的dom
Flattened DOM树表示浏览器如何将Light DOM和Shadow DOM结合在一起在屏幕上显示组件。 在开发人员工具中可以看到的就是这样的DOM树,它就是在页面上显示的。 它可能看起来像这样:
<extended-button> #shadow-root <style>…</style> <slot name="image"> <img align="center" src="boot.png" slot="image"> </slot> <span id="container"> <slot> <span>Launch</span> </slot> </span> </extended-button>
模式
如果必须在网页的HTML标记中不断使用相同的结构,则使用某个模板而不是一次又一次地编写相同的代码将很有用。 以前可以做到这一点,但是现在由于HTML
<template>
的出现,大大简化了一切,该
<template>
对现代浏览器提供了出色的支持。 该元素及其内容未显示在DOM中,但是您可以从JavaScript中使用它。 考虑一个简单的例子:
<template id="my-paragraph"> <p> Paragraph content. </p> </template>
如果将此设计包含在页面的HTML标记中,则它所描述的
<p>
标记的内容在明确地附加到文档的DOM之前不会出现在屏幕上。 例如,它可能看起来像这样:
var template = document.getElementById('my-paragraph'); var templateContent = template.content; document.body.appendChild(templateContent);
还有其他方法可以达到相同的效果,但是,正如已经提到的,模板是非常方便的标准工具,享有良好的浏览器支持。
HTML浏览器对现代浏览器的支持模板本身是有用的,但与自定义元素一起使用时,其功能已完全公开。 自定义元素是单独材料的主题,现在,要了解正在发生的事情,就足以考虑到
customElement
浏览器
customElement
允许程序员描述自己的HTML标签并指定使用这些标签创建的元素在屏幕上的外观。
定义一个使用我们的模板作为其影子DOM内容的Web组件。 将此新元素称为
<my-paragraph>
:
customElements.define('my-paragraph', class extends HTMLElement { constructor() { super(); let template = document.getElementById('my-paragraph'); let templateContent = template.content; const shadowRoot = this.attachShadow({mode: 'open'}).appendChild(templateContent.cloneNode(true)); } });
要注意的最重要的事情是,我们将使用
Node.cloneNode()方法制作的模板内容的克隆附加到影子根。
由于我们将模板的内容附加到影子DOM,因此可以在
<style>元素的模板中包含一些样式信息,然后将其封装在user元素中。 如果您使用常规DOM而不是Shadow DOM,那么整个方案将无法按预期工作。
例如,可以通过在模板中包含样式信息来对其进行如下修改:
<template id="my-paragraph"> <style> p { color: white; background-color: #666; padding: 5px; } </style> <p>Paragraph content. </p> </template>
现在,我们描述的用户元素可以在普通网页上使用,如下所示:
<my-paragraph></my-paragraph>
插槽
HTML模板有几个缺点,主要的缺点是模板包含静态标记,例如,不允许使用其帮助显示某些变量的内容,以使其与使用标准HTML的方式相同。模式。 这是
<slot>
标记所在的
<slot>
。
插槽可以看作是占位符,可让您在模板中包含自己的HTML代码。 这使您可以创建通用HTML模板,然后通过向其添加插槽来使它们可自定义。
使用
<slot>
查看以上模板的外观:
<template id="my-paragraph"> <p> <slot name="my-text">Default text</slot> </p> </template>
如果在标记中包含元素时未指定插槽的内容,或者浏览器不支持使用插槽,则
<my-paragraph>
元素将仅包含
Default text
的标准内容。
为了设置插槽的内容,您需要在
<my-paragraph>
元素中包含带有
slot
属性的HTML代码,该属性的值等效于应放置此代码的插槽的名称。
和以前一样,可以有任何东西。 例如:
<my-paragraph> <span slot="my-text">Let's have some different text!</span> </my-paragraph>
可以放置在插槽中的元素称为
Slotable元素。
请注意,在前面的示例中,我们在插槽中添加了
<span>
元素,这就是所谓的sloted元素。 它具有为
slot
属性分配的值
my-text
,即与模板中描述的插槽的
name
属性中使用的值相同的值。
处理完上述标记后,浏览器将创建以下Flattened DOM树:
<my-paragraph> #shadow-root <p> <slot name="my-text"> <span slot="my-text">Let's have some different text!</span> </slot> </p> </my-paragraph>
注意元素
#shadow-root
。 这只是影子DOM存在的一个指标。
程式化
使用Shadow DOM技术的组件可以在通用的基础上设置样式,它们可以定义自己的样式,或者以
自定义CSS属性的形式提供挂钩,以允许组件用户覆盖默认样式。
components组件中描述的样式
CSS隔离是Shadow DOM技术最显着的功能之一。 即,我们正在谈论以下内容:
- 放置了相应组件的页面的CSS选择器不会影响其内部内容。
- 组件中描述的样式不会影响页面。 它们被隔离在宿主元素中。
影子DOM中使用的CSS选择器在本地应用于组件内容。 实际上,这意味着可以在不同的组件中重用相同的标识符和类名,而无需担心名称冲突。 简单的CSS选择器还意味着使用它们的解决方案具有更好的性能。
看一下
#shadow-root
元素,它定义了一些样式:
#shadow-root <style> #container { background: white; } #container-items { display: inline-flex; } </style> <div id="container"></div> <div id="container-items"></div>
以上所有样式都是
#shadow-root
本地样式。
另外,您可以使用
<link>
标记在
#shadow-root
包含外部样式表。 这样的样式也将是本地的。
▍伪类:主机
:host
伪
:host
允许您访问包含影子DOM树的元素并设置此元素的样式:
<style> :host { display: block; } </style>
使用
:host
伪
:host
,请记住,父页面的规则比使用该伪类的元素中指定的规则具有更高的优先级。 这使用户可以从外部覆盖其中定义的主机组件样式。 另外,
:host
伪
:host
仅在影子根元素的上下文中起作用;您不能在影子DOM树之外使用它。
伪类的功能形式:
:host(<selector>)
,如果与指定的
<selector>
元素匹配,则可以访问host元素。 这是允许组件封装响应用户操作或组件状态更改的行为的好方法,并允许您根据主机组件设置内部节点的样式:
<style> :host { opacity: 0.4; } :host(:hover) { opacity: 1; } :host([disabled]) { background: grey; pointer-events: none; opacity: 0.4; } :host(.pink) > #tabs { color: pink; } </style>
with带有伪类的主题和元素:host-context(<selector>)
如果
:host-context(<selector>)
伪
:host-context(<selector>)
与host元素或其任何祖先与指定的
<selector>
元素匹配,则与host元素匹配。
此功能的常见用例是使用主题设置元素样式。 例如,通常通过将适当的类分配给
<html>
或
<body>
标签来使用主题:
<body class="lightheme"> <custom-container> … </custom-container> </body>
如果此元素是
.lightteme
的后代,
.lightteme
:
:host-context(.lightheme)
伪
:host-context(.lightheme)
将应用于
<fancy-tabs>
:
:host-context(.lightheme) { color: black; background: white; }
:host-context()
构造对于应用主题可能有用,但是为此目的,最好使用带有
自定义CSS属性的钩子。
from从外部造型组件的主体元素
可以使用其标签名称作为选择器在外部设置组件的宿主元素的样式:
custom-container { color: red; }
外部样式优先于影子DOM中定义的样式。
假设用户创建以下选择器:
custom-container { width: 500px; }
它将覆盖组件本身中定义的规则:
:host { width: 300px; }
使用这种方法,您只能样式化组件本身。 如何风格化组件的内部结构? 自定义CSS属性用于此目的。
using使用自定义CSS属性创建样式挂钩
如果组件的作者使用
自定义CSS属性为它们提供样式挂钩,则用户可以自定义组件内部结构的样式。
这种方法基于一种类似于使用
<slot>
标记时使用的机制的机制,但是在这种情况下,它适用于样式。
考虑一个例子:
<style> custom-container { margin-bottom: 60px; - custom-container-bg: black; } </style> <custom-container background>…</custom-container>
这是影子DOM树中的内容:
:host([background]) { background: var( - custom-container-bg, #CECECE); border-radius: 10px; padding: 10px; }
在这种情况下,组件使用黑色作为背景色,因为是由用户指定的。 否则,背景色将为
#CECECE
。
作为组件的作者,您有责任告诉其用户可以使用哪些特定的CSS属性。 考虑组件的开放接口的这一部分。
用于插槽的JavaScript API
Shadow DOM API提供了使用插槽的功能。
▍活动时间变更
当放置在插槽中的节点发生更改时,将引发
slotchange
事件。 例如,如果用户在Light DOM中添加或删除子节点:
var slot = this.shadowRoot.querySelector('#some_slot'); slot.addEventListener('slotchange', function(e) { console.log('Light DOM change'); });
要跟踪Light DOM中的其他类型的更改,可以在元素的构造函数中使用
MutationObserver
。
在此处阅读有关此内容的更多信息。
▍方法AssignedNodes()
如果您需要知道哪些元素与该插槽关联,则
assignedNodes()
方法可能会很有用。 调用
slot.assignedNodes()
方法可让您确切地找到插槽显示的元素。 使用
{flatten: true}
选项可以获取插槽的标准内容(如果未连接任何节点,则显示该内容)。
考虑一个例子:
<slot name='slot1'><p>Default content</p></slot>
想象一下,该插槽位于
<my-container>
组件中。
让我们看一下该组件的各种用法,以及调用
assignedNodes()
方法时将返回的内容。
在第一种情况下,我们将自己的内容添加到广告位:
<my-container> <span slot="slot1"> container text </span> </my-container>
在这种情况下,
assignedNodes()
调用将返回
[ container text ]
。 请注意,此值是节点数组。
在第二种情况下,我们不使用自己的内容填充广告位:
<my-container> </my-container>
assignedNodes()
调用将返回一个空数组-
[]
。
但是,如果将
{flatten: true}
参数传递给此方法,则对同一元素调用它会返回其默认内容:
[ Default content ]
[ Default content ]
[ Default content ]
。
另外,为了访问插槽中的元素,可以调用
assignedNodes()
以让您知道元素分配给哪个组件插槽。
事件模型
我们来谈谈在影子DOM树中弹出的事件弹出时会发生什么。 设置事件的目的时要考虑到Shadow DOM技术支持的封装。 当事件被重定向时,它看起来好像来自组件本身,而不是来自其内部元素,该内部元素位于影子DOM树中,并且是该组件的一部分。
这是从DOM阴影树传递的事件列表(此行为不是某些事件的特征):
- 焦点事件:
blur
, focus
, focusin
, focusout
。 - 鼠标事件:
click
, dblclick
, mousedown
, mouseenter
, mousemove
等。 - 车轮事件:
wheel
。 - 输入事件:
beforeinput
, input
。 - 键盘事件:
keydown
, keyup
。 - 合成事件:
compositionstart
, compositionupdate
, compositionend
。 - 拖动事件:
dragstart
, drag
, dragend
, drop
等等。
自定义事件
默认情况下,用户事件不会离开DOM阴影树。 如果要触发事件,并希望它离开Shadow DOM,则需要为其提供参数
bubbles: true
和
composed: true
。 这是此类事件的调用的外观:
var container = this.shadowRoot.querySelector('#container'); container.dispatchEvent(new Event('containerchanged', {bubbles: true, composed: true}));
支持Shadow DOM浏览器
为了确定浏览器是否支持Shadow DOM技术,您可以检查
attachShadow
的存在:
const supportsShadowDOMV1 = !!HTMLElement.prototype.attachShadow;
这是有关各种浏览器如何支持该技术的信息。
在浏览器中支持Shadow DOM技术总结
影子DOM树的行为不像常规DOM树。 特别地,根据该材料的作者,在
SessionStack库中,这是通过跟踪DOM更改的过程的复杂性来表达的,该信息需要有关哪个信息的信息才能再现页面所发生的情况。 即,
MutationObserver
用于跟踪更改。 在这种情况下,DOM影子树不会在全局范围内引发
MutationObserver
事件,这导致需要使用特殊方法来处理使用Shadow DOM的组件。
, - Shadow DOM, , , , .
亲爱的读者们! -, Shadow DOM?
