shadow DOM 是什么?

原文链接: https://bitsofco.de/what-is-the-shadow-dom/

几周之前,我写了一篇关于DOM究竟是什么的文章。回顾一下,文档对象模型 (Document Object Model) 是对HTML 文档进行解析获得的表现形式。 浏览器使用它来确定页面上要呈现的内容,并通过 Javascript 来修改页面的内容,结构或样式。

举个例子,我们看看下面这个 HTML 文件:

<!doctype html>
<html lang="en">
 <head>
   <title>My first web page</title>
  </head>
 <body>
    <h1>Hello, world!</h1>
    <p>How are you?</p>
  </body>
</html>

这个 HTML 文件会转化为如下的 DOM 树:

dom tree

在过去几年中,你可能听说过“Shadow DOM”和“Virtual DOM”等术语。 虽然它们与 DOM 有关,但事实上,它们上指的是截然不同的概念。 在本文中,我将详细介绍 shadow DOM 以及它与 DOM 的区别。 在以后的文章中,我将会介绍 virtual DOM。

一切都是全局的 👍🏾! 一切都是全局的 👎🏾

HTML 文档中的所有元素和样式以及 DOM 都位于一个全局范围内。 使用document.querySelector()方法可以访问页面上的所有元素,无论该元素在文档中的任何地方。 同样,应用于文档的 CSS 可以选择任何元素,无论元素在何处。

当我们想要将样式应用于整个文档时,这种特性非常有用。它允许我们能够仅仅使用一行代码就设置页面上的所有元素的盒模型。

* { box-sizing: border-box }

另一方面,有些时候元素需要完全封装,我们不希望它受到全局样式的影响。 一个很好的例子是第三方小部件,例如Twitter的“关注”按钮。 这个组件看起来像是这样:

假设你启用了Javascript,在控制台审查元素时,你会注意到该按钮是一个

Follow-button-widget-iframe

通过这种方式,Twitter 可以确保这个关注组件的样式将不受到其所在文档中的任何 CSS 影响,这也是唯一的方式。 虽然有一些方法尝试使用级联来获得相同的结果,但都并不理想,它们不能达到和

事实上,

A DOM within a DOM

你可以认为 shadow DOM 类似于 “A DOM within a DOM”,它是一个独立的 DOM 树,具有自己的元素和样式,与普通的 DOM 完全隔离。

虽然 shadow DOM 近年来才提供给 web 开发者使用,但浏览器 (user agent?) 已经使用了 shadow DOM 很多年,它们用来创建和设置复杂组件(如表单元素)的样式。我们来看一下范围输入元素。 首先,我们在页面上添加一个范围输入的元素:

<input type="range">

这一个元素会生成以下组件:

range-input

如果我们打开元素审查,我们可以看到这个元素实际上由几个较小的

元素组成,这几个元素用来控制轨道和滑块。

Range input shadow dom

这是使用 shadow DOM 实现的。 暴露给宿主 HTML 的元素只有简单的,但在其下面有与组件相关的元素和样式,这些元素和样式并不在 DOM 的全局范围中。

shadow DOM 的工作原理?

为了说明 shadow DOM的工作原理,让我们使用 shadow DOM 替代

首先,我们从 shadow host 开始。它是我们想要将新 shadow DOM 附加到原始DOM中的常规HTML元素。 对于像关注按钮这样的组件,它还可以包含我们希望在页面上未启用 Javascript 或不支持shadow DOM时显示的回退元素。

<span class="shadow-host">
  <a href="https://twitter.com/ireaderinokun">
     Follow @ireaderinokun
  </a>
</span>

请注意,我们不使用 元素作为shadow host,因为某些元素不能作为shadow host。

我们使用attachShadow()方法将shadow DOM 附加到我们的 shadow host 上

const shadowEl = document.querySelector(".shadow-host");
const shadow = shadowEl.attachShadow({mode: 'open'});

This will create an empty shadow root as a child of our shadow host. The shadow root is the start of a new shadow DOM in the way that the <html> element is the start of the original DOM. We can see our shadow root in the devtools inspector by the #shadow-root.

这会在我们的shadow root中创建一个空的shadow root 作为子元素,shadow root 是一个shadow DOM被添加到原始DOM的html元素标志,我们可以通过#shadow-root在devtools检查器中看到我们的shadow root。

Empty shadow root

尽管在 shadow root 下的常规HTML元素在 devtools 中是可见的,但是在页面上,它们被 shadow root 接管,不再可见。

接下来,我们要创建内容以形成新的 shadow tree。shadow tree 就像一个DOM树,区别在于它是针对阴影 DOM 的而不是常规 DOM 。 要创建我们的关注按钮,我们所需要的只是一个新的<a>元素,同时带有一个图标。

const link = document.createElement("a");
link.href = shadowEl.querySelector("a").href;
link.innerHTML = `
    <span aria-label="Twitter icon"></span> 
    ${shadowEl.querySelector("a").textContent}
`;

我们将这个新元素添加到我们的 shadow DOM 中,就和使用appendChild()方法添加子元素一样。

shadow.appendChild(link);

这时,我们的元素看起来像是这样:

Plain text of "Follow-@ireaderinokun"

最后,我们可以通过创建