Web Components系列(四) —— 认识 Shadow DOM

shadowdom.001

前言#

在初涉前端之时,我就一直在好奇一个问题,为什么像:

  • <input/>
  • <select></select>
  • <audio></audio>
  • <video></video>
  • ……

等等这些标签,看起来似乎很简单,可为什么可以展现出那么丰富复杂的布局?当时我给自己的解释是:这些标签都是系统控制渲染的。

现在想想,那么解释好像有点道理,但终归没有涉及到本质原因,专家级的解释应该是:以上这些元素都是以组件的方式存在,所展现出来的那些布局都是在组件内部定义好的,如果页面引用了这些元素标签,那它内部的布局都会渲染在页面上

在介绍 Web Components 时讲到,它的第二项技术规范为 Shadow DOM。通过了解 Shadow DOM 的相关知识,或许可以解开上面的疑惑。

查看默认组件的 Shadow DOM#

有人可能疑惑,既然说文章开头列举的那些元素是组件,那为什么我在开发者工具中看到的结构是这样的:

image-20220209164432603

有什么办法可以看到各个组件内部的 DOM 结构吗?答案是有的,步骤如下:

第一步:打开开发者工具,点击右上角的设置图标:

image-20220209164713615

第二步:找到偏好设置 - 元素,然后对 “显示用户代理 Shadow DOM” 进行选中:

image-20220209164834841

这时候,我们再查看那些默认组件的内部结构,如下所示:

image-20220209165056702

可以看到,这些默认标签下都包含一个 “shadow root” 而在 shadow root 中包含的是具体布局:

image-20220209165539233

一个看似简简单单的 audio 元素,里面的布局还蛮复杂的。这不禁让我想到一句话:功夫都在舞台外

在上面的截图中,我们看到了 “shadow root” ,它其实是 Shadow DOM 的一部分。

Shadow DOM 的概念#

在介绍概念之前,我们先来看看 “shadow” 这个单词的中文释义:

image-20220209161512842

Shadow DOM,翻译过来就是 “影子 DOM” 。

影子当然都是藏在暗处,不容易让人发现的,就像文章开头提到的那些默认元素,如果不通过设置,我们表面上看到的就是简单的一个标签而已。

专业的解释就是:Shadow DOM 是 HTML 的一个规范 ,它允许浏览器开发者封装自己的 HTML 标签、CSS 样式和特定的 Javascrip 代码,同时也可以让开发人员创建类似 <video> 这样的自定义标签。

Shadow DOM 的意义#

“组件化” 备受追捧的原因自然是因为它独特的魅力,我们只需要将定义好的组件通过简单的一组标签引入页面,就可以得到预定好的效果,并且可以在多处使用。

而 Shadow DOM 能在 Web Components 体系中占据重要的地位,是因为其具有良好的密封性,主要表现在:

  • 隐藏标记、样式和行为;
  • 保持代码隔离,保证页面的干净整洁,各组件内部代码互不影响;
  • 隐藏实现细节,便于使用更强大的语法或功能。

这就意味着,如果我们使用了 Shadow DOM,那就可以在它内部随意的发挥,而不必担心这些发挥会影响到页面的其他部分,变相地给了开发人员极大的自由。

想想曾经小心翼翼地定义样式、绑定事件的时光吧,怀念吗?

Shadow DOM 结构#

Shadow DOM 允许将隐藏的 DOM 树附加到常规的 DOM 树中 —— 它以 Shadow root 节点为起始根节点,在这个根节点的下方,可以是任意元素,和普通的 DOM 元素一样,借用网上的一张图片:

img

下面是我根据自己的理解画出来的:

image-20220209182955624

大家根据自己喜好,看哪一张更容易理解就对着哪张看,都无所谓的。对应到实际的文档中,其结构如下:

image-20220209224058708

在以上的结构图中,我们看到了几个陌生的名词,包括我们在之前看到的 “shadow root”,它们都是 Shadow DOM 的术语,接下来我解释一下它们各自的含义。

Shadow DOM 术语#

Shadow host#

一个常规 DOM 节点,Shadow DOM 会被附加到这个节点上。

Shadow tree#

Shadow DOM 内部的 DOM 树。

Shadow boundary#

Shadow DOM 分界线。Shadow DOM 结束的地方,也是常规 DOM 开始的地方。

Shadow root#

Shadow tree 的根节点。

用法#

挂载 Shadow DOM#

可使用 Element.attachShadow() 方法给指定的元素挂载一个 Shadow DOM,并且返回对 ShadowRoot 的引用。

let hostEle = document.getElementById("myCard");
let shadowroot = hostEle.attachShadow({mode: "open"});

控制 Shadow DOM#

你可以使用同样的方式来操作 Shadow DOM,就和操作常规 DOM 一样 —— 例如添加子节点、设置属性,以及为节点添加自己的样式(例如通过 element.style 属性),或者为整个 Shadow DOM 添加样式(例如在 <style> 元素内添加样式)。不同的是,Shadow DOM 内部的元素始终不会影响到它外部的元素(除了 :focus-within 元素内添加样式),这为封装提供了便利。

注意事项#

如果一个元素底下已经有一个 Shadow DOM 挂载,继续给它挂载的话,会报错:

image-20220209224818484

结束语#

Shadow DOM 的主要作用就是其封装的特性,使得各组件的内部代码互不干扰,提供一个安全的开发运行环境。

关于 Shadow DOM 的基本概念就先介绍这么多,接下来将介绍它的操作方法。

~

~ 本文完,感谢阅读!

~

学习有趣的知识,结识有趣的朋友,塑造有趣的灵魂!

大家好,我是〖编程三昧〗的作者 隐逸王,我的公众号是『编程三昧』,欢迎关注,希望大家多多指教!

本作品采用《CC 协议》,转载必须注明作者和本文链接