You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
通常,你创建 DOM 节点并将其附加至其他元素作为子项。 借助于 shadow DOM,您可以创建作用域 DOM 树,该 DOM 树附加至该元素上,但与其自身真正的子项分离开来。这一作用域子树称为影子树。被附着的元素称为影子宿主。 您在影子中添加的任何项均将成为宿主元素的本地项,包括 <style>。 这就是 shadow DOM 实现 CSS 样式作用域的方式
通常,创建 DOM 节点并将它们作为子元素追加到另一个元素中。借助于 shadow DOM,创建一个作用域 DOM 树,附该 DOM 树附加到元素上,但它与实际的子元素是分离的。这个作用域的子树称为 影子树,被附着的元素称为影子宿主。向影子树添加的任何内容都将成为宿主元素的本地元素,包括 <style>,这就是 影子DOM 实现 CSS 样式作用域的方式。
概述
Web Components 是一套不同的技术,允许你创建可重用的定制元素,它们的功能封装在你的代码之外,你可以在 Web 应用中使用它们。
Web组件由四部分组成:
Shadow DOM(影子DOM)
HTML templates(HTML模板)
Custom elements(自定义元素)
HTML Imports(HTML导入)
在本文中主要讲解 Shadow DOM(影子DOM)
Shadow DOM 这款工具旨在构建基于组件的应用。因此,可为网络开发中的常见问题提供解决方案:
Shadow DOM
本文假设你已经熟悉 DOM 及其它的 Api 的概念。如果不熟悉,可以在这里阅读关于它的详细文章—— https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction。
阴影 DOM 只是一个普通的 DOM,除了两个区别:
创建/使用的方式
与页面其他部分有关的行为方式
通常,你创建 DOM 节点并将其附加至其他元素作为子项。 借助于 shadow DOM,您可以创建作用域 DOM 树,该 DOM 树附加至该元素上,但与其自身真正的子项分离开来。这一作用域子树称为影子树。被附着的元素称为影子宿主。 您在影子中添加的任何项均将成为宿主元素的本地项,包括 <style>。 这就是 shadow DOM 实现 CSS 样式作用域的方式
通常,创建 DOM 节点并将它们作为子元素追加到另一个元素中。借助于 shadow DOM,创建一个作用域 DOM 树,附该 DOM 树附加到元素上,但它与实际的子元素是分离的。这个作用域的子树称为 影子树,被附着的元素称为影子宿主。向影子树添加的任何内容都将成为宿主元素的本地元素,包括
<style>
,这就是 影子DOM 实现 CSS 样式作用域的方式。创建 shadow DOM
影子根是附加到“宿主”元素的文档片段,元素通过附加影子根来获取其 shadow DOM。要为元素创建阴影 DOM,调用
element.attachShadow()
:规范定义了元素列表,这些元素无法托管影子树,元素之所以在所选之列,其原因如下:
浏览器已为该元素托管其自身的内部 shadow DOM(
<textarea>
、<input>
)。让元素托管 shadow DOM 毫无意义 ()。
例如,以下方法行不通:
Light DOM
这是组件用户写入的标记。该 DOM 不在组件 shadow DOM 之内,它是元素的实际孩子。假设已经创建了一个名为
<extended-button>
的定制组件,它扩展了原生 HTML 按钮组件,此时希望在其中添加图像和一些文本。代码如下:“extension -button” 是定义的定制组件,其中的 HTML 称为 Light DOM,该组件由用户自己添加。
这里的 Shadow DOM 是你创建的组件
extension-button
。Shadow DOM是 组件的本地组件,它定义了组件的内部结构、作用域 CSS 和 封装实现细节。扁平 DOM 树
浏览器将用户创建的 Light DOM 分发到 Shadow DOM,并对最终产品进行渲染。扁平树是最终在 DevTools 中看到的以及页面上呈渲染的对象。
模板 (Templates)
如果需要 Web 页面上重复使用相同的标签结构时,最好使用某种类型的模板,而不是一遍又一遍地重复相同的结构。这在以前也是可以实现,但是 HTML 元素(在现代浏览器中得到了很好的支持)使它变得容易得多。此元素及其内容不在 DOM 中渲染,但可以使用 JavaScript 引用它。
一个简单的例子:
这不会出现在页面中,直到使用 JavaScrip t引用它,然后使用如下方式将其追加到 DOM 中:
到目前为止,已经有其他技术可以实现类似的行为,但是,正如前面提到的,将其原生封装起来是非常好的,Templates 也有相当不错的浏览器支持:
模板本身是有用的,但它们与自定义元素配合会更好。 可以
customElement
Api 能定义一个自定义元素,并且告知 HTML 解析器如何正确地构造一个元素,以及在该元素的属性变化时执行相应的处理。让我们定义一个 Web 组件名为
<my-paragraph>
,该组件使用之前模板作为它的 Shadow DOM 的内容:这里需要注意的关键点是,我们向影子根添加了模板内容的克隆,影子根是使用 Node.cloneNode() 方法创建的。
因为将其内容追加到一个 Shadow DOM 中,所以可以在模板中使用 <style> 元素的形式包含一些样式信息,然后将其封装在自定义元素中。如果只是将其追加到标准 DOM 中,它是无法工作。
例如,可以将模板更改为:
现在自定义组件可以这样使用:
元素
模板有一些缺点,主要是静态内容,它不允许我们渲染变量/数据,好可以让我们按照一般使用的标准 HTML 模板的习惯来编写代码。Slot 是组件内部的占位符,用户可以使用自己的标记来填充。让我们看看上面的模板怎么使用
slot
:如果在标记中包含元素时没有定义插槽的内容,或者浏览器不支持插槽,
<my-paragraph>
就只展示文本 “Default text”。为了定义插槽的内容,应该在
<my-paragraph>
元素中包含一个 HTML 结构,其中的 slot 属性的值为我们定义插槽的名称:可以插入插槽的元素称为 Slotable; 当一个元素插入一个插槽时,它被称为开槽 (slotted)。
注意,在上面的例子中,插入了一个
<span>
元素,它是一个开槽元素,它有一个属性slot
,它等于my-text
,与模板中的slot
定义中的name
属性的值相同。在浏览器中渲染后,上面的代码将构建以下扁平 DOM 树:
设定样式
使用 shadow DOM 的组件可通过主页来设定样式,定义其自己的样式或提供钩子(以 CSS 自定义属性的形式)让用户替换默认值。
组件定义的样式
作用域 CSS 是 Shadow DOM 最大的特性之一:
shadow DOM 内部使用的 CSS 选择器在本地应用于组件实际上,这意味着我们可以再次使用公共vid/类名,而不用担心页面上其他地方的冲突,最佳做法是在 Shadow DOM 内使用更简单的 CSS 选择器,它们在性能上也不错。
看看在 #shadow-root 定义了一些样式的:
上面例子中的所有样式都是#shadow-root的本地样式。使用元素在#shadow-root中引入样式表,这些样式表也都属于本地的。
:host 伪类选择器
使用
:host
伪类选择器,用来选择组件宿主元素中的元素 (相对于组件模板内部的元素)。当涉及到
:host
选择器时,应该小心一件事:父页面中的规则具有比元素中定义的:host
规则具有更高的优先级,这允许用户从外部覆盖顶级样式。而且:host
只在影子根目录下工作,所以你不能在Shadow DOM 之外使用它。如果
:host(<selector>)
的函数形式与<selector>
匹配,你可以指定宿主,对于你的组件而言,这是一个很好的方法,它可让你基于宿主将对用户互动或状态的反应行为进行封装,或对内部节点进行样式设定::host-context()
:host-context(<selector>)
或其任意父级与 匹配,它将与组件匹配。 例如,在文档的元素上可能有一个用于表示样式主题 (theme) 的 CSS 类,而我们应当基于它来决定组件的样式。比如,很多人都通过将类应用到 或 进行主题化:
在下面的例子中,只有当某个祖先元素有 CSS 类theme-light时,我们才会把background-color样式应用到组件内部的所有元素中:
/deep/
组件样式通常只会作用于组件自身的 HTML 上,我们可以使用
/deep/
选择器,来强制一个样式对各级子组件的视图也生效,它不但作用于组件的子视图,也会作用于组件的内容。在下面例子中,我们以所有的元素为目标,从宿主元素到当前元素再到 DOM 中的所有子元素:
/deep/
选择器还有一个别名>>>
,可以任意交替使用它们。从外部为组件设定样式
有几种方法可从外部为组件设定样式:最简单的方法是使用标记名称作为选择器,如下
外部样式比在 Shadow DOM 中定义的样式具有更高的优先级。
例如,如果用户编写选择器:
它将覆盖组件的样式:
对组件本身进行样式化只能到此为止。但是如果人想要对组件的内部进行样式化,会发生什么情况呢?为此,我们需要 CSS 自定义属性。
使用 CSS 自定义属性创建样式钩子
如果组件的开发者通过 CSS 自定义属性提供样式钩子,则用户可调整内部样式。其思想类似于
<slot>
,但适用于样式。看看下面的例子:
在其 shadow DOM 内部:
在本例中,该组件将使用 black 作为背景值,因为用户指定了该值,否则,背景颜色将采用默认值
#CECECE
。在 JS 中使用 slot
Shadow DOM API 提供了使用 slot 和分布式节点的实用程序,这些实用程序在编写自定义元素时迟早派得上用场。
slotchange 事件
当
slot
的分布式节点发生变化时,slotchange
事件将触发。例如,如果用户从 light DOM 中添加/删除子元素。要监视对 light DOM 的其他类型的更改,可以在元素的构造函数中使用
MutationObserver
。以前讨论过 MutationObserver 的内部结构以及如何使用它。assignedNodes() 方法
有时候,了解哪些元素与 slot 相关联非常有用。调用
slot.assignedNodes()
可查看 slot 正在渲染哪些元素。{flatten: true}
选项将返回 slot 的备用内容(前提是没有分布任何节点)。让我们看看下面的例子:
假设这是在一个名为
<my-container>
的组件中。看看这个组件的不同用法,以及调用
assignedNodes()
的结果是什么:在第一种情况下,我们将向
slot
中添加我们自己的内容:调用
assignedNodes()
会得到[<span slot= " slot1 " > container text </span>]
,注意,结果是一个节点数组。在第二种情况下,将内容置空:
调用
assignedNodes()
的结果将返回一个空数组[]
。在第三种情况下,调用
slot.assignedNodes({flatten: true})
,得到结果是:[<p>默认内容</p>]
。此外,要访问
slot
中的元素,可以调用assignedNodes()
来查看元素分配给哪个组件slot
。事件模型
值得注意的是,当发生在 Shadow DOM 中的事件冒泡时,会发生什么。
当事件从 Shadow DOM 中触发时,其目标将会调整为维持 Shadow DOM 提供的封装。也就是说,事件的目标重新进行了设定,因此这些事件看起来像是来自组件,而不是来自 Shadow DOM 中的内部元素。
下面是从 Shadow DOM 传播出去的事件列表(有些没有):
自定义事件
默认情况下,自定义事件不会传播到 Shadow DOM 之外。如果希望分派自定义事件并使其传播,则需要添加
bubbles: true
和composed: true
选项。让我们看看派发这样的事件是什么样的:
浏览器支持
如希望获得 shadow DOM 检测功能,请查看是否存在 attachShadow:
有史以来第一次,我们拥有了实施适当 CSS 作用域、DOM 作用域的 API 原语,并且有真正意义上的组合。 与自定义元素等其他网络组件 API 组合后,shadow DOM 提供了一种编写真正封装组件的方法,无需花多大的功夫或使用如 <iframe> 等陈旧的东西。
代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。
你的点赞是我持续分享好东西的动力,欢迎点赞!
欢迎加入前端大家庭,里面会经常分享一些技术资源。
The text was updated successfully, but these errors were encountered: