_小生_

什么是Shadow DOM?

原文链接: bitsofco.de

几周前,我写了一篇关于究竟是什么DOM的文章。回顾一下,文档对象模型是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树。

  • html

    • head

      • title
        • My first web page
    • body

      • h1

        • Hello, world!
      • p

        • How are you?

在过去几年中,您可能听说过“Shadow DOM”和“Virtual DOM”等术语。这些虽然当然与原始DOM有关,但它们指的是截然不同的概念。在本文中,我将详细介绍Shadow DOM以及它与原始DOM的区别。在以后的文章中,我将对虚拟DOM进行分析。

一切都是 global 的👍🏾!等等,一切都是 global 的👎🏾

HTML文档中的所有元素和样式以及DOM都位于一个全局范围内。页面上的任何元素都可以通过document.querySelector()方法访问,无论它在文档中的嵌套程度如何或放置在何处。同样,应用于文档的CSS可以选择任何元素,无论它在何处。

当我们想要将样式应用于整个文档时,可以直接使用*选择页面上的每个元素并将它们的盒模型进行修改。box-sizing

* { box-sizing: border-box }


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

Follow @ireaderinokun

Twitter follow @ireaderinokun

假设您启用了Javascript并且检查了元素,您会注意到该按钮是一个&lt;iframe&gt;元素,您实际看到的样式按钮其实是加载一个小文档。

Follow-button-widget-iframe

这是Twitter可以确保其小部件的预期样式不受文档中的任何CSS影响的唯一方式。尽管有一些方法可以使用级联来尝试实现相同的结果,但是没有其他方法可以提供与&lt;iframe&gt;相同的效果。

创建Shadow DOM是为了允许在Web平台上本地封装和组件化,而不必依赖像&lt;iframe&gt;这样的工具,而这些工具实际上并不是为此目的而制作的。

DOM中的DOM

您可以将shadow DOM视为“DOM中的DOM”。它是自己独立的DOM树,具有自己的元素和样式,与原始DOM完全隔离。

虽然最近才指定供Web作者使用,但用户代理多年来一直使用shadow DOM来创建和设置复杂组件(如表单元素)。我们来看一下input。要在页面上创建一个,我们所要做的就是添加以下元素:

<input type="range">


如果我们深入挖掘,我们会看到这个&lt;input&gt;元素实际上是由几个较小的<div>元素组成,控制着轨道和滑块本身。

Range input shadow dom

这是使用shadow DOM实现的。暴露给主机HTML的元素记录了简单的&lt;input&gt;,但在其下面有与组件相关的元素和样式,它们不构成DOM全局​​范围的一部分。

shadow DOM如何工作

为了说明shadow DOM的工作原理,让我们使用shadow DOM而不是&lt;iframe&gt;来重新创建Twitter“follow”按钮。

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

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


请注意,我们不仅仅使用<a>元素作为dow host,因为某些元素(主要是交互元素)不能是dow host。

要将阴影DOM附加到我们的主机,我们使用attachShadow()方法。

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


这将创建一个空的shadow root作为我们的shadow host的子项。shadow root 是新的shadow DOM的开始,其方式是&lt;html&gt;元素是原始DOM的开头。我们可以通过#shadow-root 在devtools检查器中看到我们的shadow-root。

Empty shadow root

虽然常规HTML子项在检查器中是可见的,但是当shadow root接管时,它们在页面上不再可见。

接下来,我们要创建内容以形成新的shadow tree。shadow tree就像一个DOM树,但是对于shadow 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"

最后,我们可以通过创建一个&lt;style&gt;元素并将其附加到阴影根来添加一些样式。

const styles = document.createElement("style");
styles.textContent = 
a, span {
  vertical-align: top;
  display: inline-block;
  box-sizing: border-box;
}

a {
    height: 20px;
    padding: 1px 8px 1px 6px;
    background-color: #1b95e0;
    color: #fff;
    border-radius: 3px;
    font-weight: 500;
    font-size: 11px;
    font-family:'Helvetica Neue', Arial, sans-serif;
    line-height: 18px;
    text-decoration: none;   
}

a:hover {  background-color: #0c7abf; }

span {
    position: relative;
    top: 2px;
    width: 14px;
    height: 14px;
    margin-right: 3px;
    background: transparent 0 0 no-repeat;
    background-image: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2072%2072%22%3E%3Cpath%20fill%3D%22none%22%20d%3D%22M0%200h72v72H0z%22%2F%3E%3Cpath%20class%3D%22icon%22%20fill%3D%22%23fff%22%20d%3D%22M68.812%2015.14c-2.348%201.04-4.87%201.744-7.52%202.06%202.704-1.62%204.78-4.186%205.757-7.243-2.53%201.5-5.33%202.592-8.314%203.176C56.35%2010.59%2052.948%209%2049.182%209c-7.23%200-13.092%205.86-13.092%2013.093%200%201.026.118%202.02.338%202.98C25.543%2024.527%2015.9%2019.318%209.44%2011.396c-1.125%201.936-1.77%204.184-1.77%206.58%200%204.543%202.312%208.552%205.824%2010.9-2.146-.07-4.165-.658-5.93-1.64-.002.056-.002.11-.002.163%200%206.345%204.513%2011.638%2010.504%2012.84-1.1.298-2.256.457-3.45.457-.845%200-1.666-.078-2.464-.23%201.667%205.2%206.5%208.985%2012.23%209.09-4.482%203.51-10.13%205.605-16.26%205.605-1.055%200-2.096-.06-3.122-.184%205.794%203.717%2012.676%205.882%2020.067%205.882%2024.083%200%2037.25-19.95%2037.25-37.25%200-.565-.013-1.133-.038-1.693%202.558-1.847%204.778-4.15%206.532-6.774z%22%2F%3E%3C%2Fsvg%3E);
}
;

shadow.appendChild(styles);


这是我们的最后一个要素:

Follow @ireaderinokun Twitter follow @ireaderinokun

DOM与shadow DOM

在某些方面,shadow DOM是DOM的“精简”版本。与DOM一样,它是HTML元素的表示,用于确定在页面上呈现的内容并启用元素的修改。但与DOM不同,shadow DOM不是基于完整的独立文档。正如其名称所示,shadow DOM始终附加到常规DOM中的元素。没有DOM,shadow DOM就不存在了。