你需要知道的Shadow DOM

本文作者为 360 奇舞团前端开发工程师

什么是 Shadow DOM

Shadow DOM 简介

在现代的前端开发中,组件化和模块化已经成为构建可维护和可扩展 Web 程序的关键。随着 Web 程序规模的增长,很容易遇到样式和逻辑冲突的问题。为了解决这些问题,Web 标准引入了 Shadow DOM,可以帮助我们更好地隔离和封装组件。提到 Shadow DOM 就不得不提 Web Components。Shadow DOM 是 Web Components 标准的一部分,是 Web Components 中的一个关键技术。它提供了一种将 HTML 结构、样式和行为封装在一个独立的、封闭的 DOM 中的机制。这意味着,使用 Shadow DOM 可以将组件的样式和结构隐藏在组件作用域内,防止其与全局样式或逻辑发生冲突。

Web Components 简介

既然 Shadow DOM 是 Web Components 标准的一部分,那么什么是 Web Components?Web Components 是旨在帮助开发者能够创建可重用的组件,这些组件可以在不同的 Web 应用程序中使用的技术。Web Components 由以下三项主要技术组成:

  • Custom Elements: 一组 JavaScript API,允许开发者定义自己的 HTML 元素和其行为。

  • Shadow DOM: 一组 JavaScript API,提供了一种将 HTML 结构、样式和行为封装在一个独立的、可隔离的作用域内的机制。

  • HTML Templates: 允许开发者定义 HTML 结构的模板,可以作为自定义元素结构的基础被多次重用。

尽管 Shadow DOM 是 Web Components 中的一个关键技术,如果只使用 Shadow DOM 来隔离样式和封装 DOM 结构,也可以单独使用 Shadow DOM,而不定义 Custom Elements 和使用其他 Web Components 技术。比如下面我们先看一下简单的例子。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Shadow DOM</title>
</head>

<body>

  <div id="shadow-element">This content is outside Shadow DOM.</div>

  <script>
    const hostElement = document.getElementById('shadow-element');

    const shadowRoot = hostElement.attachShadow({ mode: 'open' });

    const paragraph = document.createElement('p');
    paragraph.textContent = 'This content is inside Shadow DOM.';
    shadowRoot.appendChild(paragraph);

  </script>

</body>

</html>

运行效果如下:093f5c3e288d47e7d66f451f9b2a291f.png

Shadow DOM 核心概念

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

69c5d4460ddca4040c7d81bda196db4c.png
image.png
  • Shadow host:一个常规 DOM 节点,作为 Shadow DOM 的容器。Shadow DOM 会被附加到这个节点上。Shadow host 将 Shadow DOM 插入到文档中,作为 Shadow DOM 的入口点。它是外部 DOM 和 Shadow DOM 之间的连接点。

  • Shadow tree:Shadow DOM 内部的 DOM 结构。包含完整的的 HTML 结构、样式和行为,形成一个独立的渲染上下文。

  • Shadow boundary:Shadow DOM 结束的地方,也是常规 DOM 开始的地方。确保样式和 DOM 结构的隔离。

  • Shadow root: Shadow tree 的根节点。包含 Shadow DOM 的所有内容,它是 Shadow DOM 内部结构的入口。通过创建 Shadow Root,可以将 Shadow DOM 附加到 Shadow host 上。

Shadow DOM 都不是一个新事物——在过去的很长一段时间里,浏览器用它来封装一些元素的内部结构。以一个有着默认播放控制按钮的 <video> 元素为例。你所能看到的只是一个 <video> 标签,实际上,在它的 Shadow DOM 中,包含了一系列的按钮和其他控制器。Shadow DOM 标准允许你为你自己的元素(custom element)维护一组 Shadow DOM。

我们可以 Dev Tools 中通过 Network -> Add -> Preferences -> Show user agent shadow DOM 来查看 video 的内部信息(操作步骤如下)。

0a8192b6e255e316303cde3d142078f0.png 56742c689a9629f094ac9c080251f7f2.png 226e85a1e7517a7862666526f16bc4b6.png

Shadow DOM 使用

基本用法

要在项目中使用Shadow DOM,需要以下步骤:

  • 获取/创建 Shadow host 作为 Shadow DOM 的容器。

  • 创建 Shadow DOM :使用元素的 attachShadow 方法来创建 Shadow DOM。这个方法接受一个参数,即一个包含 mode 属性的配置对象,该属性可以是 'open' 或 'closed'。'open' 表示可以通过 JavaScript 访问 Shadow DOM,而 'closed' 则表示无法通过 JavaScript 直接访问。

  • 定义 Shadow DOM 的内容:在创建的 Shadow DOM 中,你可以添加自己的 HTML 结构和样式。

在开始之前先看下面的代码:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Shadow DOM Example</title>
  <style>
    button {
      background-color: black;
      color: #fff;
      padding: 10px 20px;
      font-size: 16px;
    }
  </style>
</head>

<body>
  <button>normal</button>
  <div id="shadow-container"></div>

  <script>
    const buttonContainer = document.getElementById('shadow-container');
    const shadow = buttonContainer.attachShadow({ mode: 'open' });

    const style = document.createElement('style');
    style.textContent = `
      button {
        background-color: red;
        color: #fff;
        padding: 10px 20px;
        font-size: 16px;
      }
    `;

    const button = document.createElement('button');
    button.textContent = 'In Shadow DOM';

    shadow.appendChild(style);
    shadow.appendChild(button);
  </script>
</body>

</html>
8d65a745b6938a2bbaccdb6abd2c093c.png

在上面的代码中,我们创建了一个普通按钮和一个使用Shadow DOM的按钮。Shadow DOM中的按钮样式独立于页面上的全局样式,因此它具有不同的背景颜色(红色)。Shadow DOM的一个重要特性是它提供了封装性,使得内部的样式和结构不会影响到外部的文档。

Shadow DOM Mode

同时我们可以注意到 { mode: 'open' } 传的是 open,mode 的取值为 open 和 closed

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Shadow DOM Example</title>
</head>

<body>

  <div>
    <p>paragraph</p>
  </div>
  <div id="host"></div>

  <script>
    const ele = document.querySelector('#host');
    const shadowRoot = ele.attachShadow({ mode: 'open' });
    shadowRoot.innerHTML = `<p>Shadow DOM</p>`;
    ele.shadowRoot.querySelector('p').innerText = 'change from outside';
    ele.shadowRoot.querySelector('p').style.color = 'red';
  </script>
</body>

</html>
bf10ddcb0e809f4586c33e9feb9e4fa3.png
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Shadow DOM Example</title>
</head>

<body>

  <div>
    <p>paragraph</p>
  </div>
  <div id="host"></div>

  <script>
    const ele = document.querySelector('#host');
    const shadowRoot = ele.attachShadow({ mode: 'closed' });
    shadowRoot.innerHTML = `<p>Shadow DOM</p>`;
    ele.shadowRoot.querySelector('p').innerText = 'change from outside';
    ele.shadowRoot.querySelector('p').style.color = 'red';
  </script>
</body>

</html>
bd1e46be72fc406a8469eea4d7dda15a.png

通过上面的例子可以看到,open模式允许外部访问Shadow DOM,而closed模式则不允许。

Shadow DOM 优势

  • 封装:Shadow DOM允许开发者将组件的HTML、样式和行为封装在一起,使其成为一个独立的单元。这有助于降低组件之间的耦合度。

  • 隔离:Shadow DOM内部的DOM结构对外部不可见,从而避免了全局作用域的样式和脚本冲突。这有助于提高代码的可靠性。

Shadow DOM 劣势

  • 学习曲线:使用和理解 Shadow DOM 需要一定的学习曲线,特别是对于那些不熟悉 Web 组件概念的开发者。

  • 调试困难:由于 Shadow DOM 的封装性质,调试组件内部的 DOM 结构和样式可能变得更加困难。

  • 浏览器支持:虽然大多数现代浏览器都支持 Shadow DOM,但在某些特定环境或设备上,可能会遇到兼容性问题。

Shadow DOM 和 Iframe

Shadow DOM 更适合于构建具有封装性和局部样式的组件,而 <iframe> 更适合于加载独立的文档、应用程序或提供跨域通信的场景。

Shadow DOM 使用场景

笔者使用 Shadow DOM 的场景是在开发 Chrome Extension 过程中为了避免  Chrome Extension 注入的页面与原页面产生样式冲突,因此选择了 Shadow DOM 。如果有组件化开发、样式隔离、浏览器插件开发、Web组件库等场景时可以选择 Shadow DOM 。

参考资料

https://developer.mozilla.org/zh-CN/docs/Web/API/Web_components

https://developer.mozilla.org/zh-CN/docs/Web/API/Web_components/Using_shadow_DOM

- END -

关于奇舞团

奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

6edef577db609078d36ad0ded190a866.png

猜你喜欢

转载自blog.csdn.net/qiwoo_weekly/article/details/134702523