Skip to content

Shadow DOM

Shadow DOM 是 Web Components 技术栈的核心组成部分之一,它允许开发者创建封装的 DOM 树,与主文档的 DOM 树隔离。这种隔离性使得组件可以拥有自己的样式、结构和行为,而不会影响到页面的其他部分。

什么是 Shadow DOM

Shadow DOM 是一种将 DOM 子树附加到元素上的技术,这些子树在主 DOM 树中是不可见的,也不会被常规的 DOM 查询方法(如 querySelector)访问到。

核心概念

  • Shadow 宿主(Shadow Host):主 DOM 树中的元素,Shadow DOM 附加到这个元素上。
  • Shadow 根(Shadow Root):Shadow DOM 树的根节点,通过 attachShadow() 方法创建。
  • Shadow 树:包含在 Shadow Root 内的 DOM 子树,与主 DOM 树隔离。
  • 封装性:Shadow DOM 内的样式和脚本不会泄漏到外部,外部的样式和脚本也不会影响到内部。

工作原理

创建 Shadow DOM

要创建 Shadow DOM,需要使用 attachShadow() 方法,该方法接受一个配置对象,其中 mode 属性可以设置为 'open''closed'

javascript
// 创建一个 Shadow DOM 宿主
const host = document.createElement('div')
document.body.appendChild(host)

// 附加 Shadow DOM
const shadowRoot = host.attachShadow({ mode: 'open' })

// 向 Shadow DOM 中添加内容
shadowRoot.innerHTML = `
  <style>
    p { color: red; }
  </style>
  <p>Hello from Shadow DOM!</p>
`

隔离机制

  1. 样式隔离:Shadow DOM 内的样式只影响内部元素,不会影响外部元素。
  2. DOM 隔离:外部的 DOM 查询方法无法访问 Shadow DOM 内部的元素。
  3. 事件传播:事件会从 Shadow DOM 内部冒泡到外部,但会进行重定向,确保事件目标的正确性。

模式区别

  • open 模式:可以通过 host.shadowRoot 访问 Shadow Root。
  • closed 模式:无法通过 host.shadowRoot 访问 Shadow Root,提供更强的封装性。

使用案例

1. 自定义元素

Shadow DOM 最常见的使用场景是创建自定义元素,实现组件的封装:

javascript
class MyComponent extends HTMLElement {
  constructor() {
    super()

    // 创建 Shadow DOM
    const shadowRoot = this.attachShadow({ mode: 'open' })

    // 添加样式和内容
    shadowRoot.innerHTML = `
      <style>
        .container {
          padding: 10px;
          background-color: #f0f0f0;
          border-radius: 5px;
        }
      </style>
      <div class="container">
        <slot></slot>
      </div>
    `
  }
}

// 注册自定义元素
customElements.define('my-component', MyComponent)

2. 封装第三方组件

使用 Shadow DOM 可以封装第三方组件,避免样式冲突:

javascript
class ThirdPartyWrapper extends HTMLElement {
  constructor() {
    super()

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

    // 加载第三方组件的样式和脚本
    shadowRoot.innerHTML = `
      <link rel="stylesheet" href="third-party.css">
      <div id="third-party-container"></div>
      <script src="third-party.js"></script>
    `
  }
}

customElements.define('third-party-wrapper', ThirdPartyWrapper)

3. 样式封装

在复杂的应用中,使用 Shadow DOM 可以避免样式冲突:

javascript
class StyledComponent extends HTMLElement {
  constructor() {
    super()

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

    // 封装样式
    shadowRoot.innerHTML = `
      <style>
        /* 这些样式只影响组件内部 */
        button {
          background-color: blue;
          color: white;
          padding: 8px 16px;
          border: none;
          border-radius: 4px;
        }
      </style>
      <button>Click me</button>
    `
  }
}

customElements.define('styled-button', StyledComponent)

浏览器支持

Shadow DOM 在现代浏览器中得到了广泛支持,包括 Chrome、Firefox、Safari 和 Edge。对于不支持 Shadow DOM 的浏览器,可以使用 polyfill 来提供兼容。

最佳实践

  1. 合理使用 Shadow DOM:只在需要封装的场景下使用,避免过度使用。
  2. 使用 slots:利用 <slot> 元素允许外部内容插入到 Shadow DOM 中。
  3. 注意事件处理:了解 Shadow DOM 中的事件传播机制,确保事件处理的正确性。
  4. 性能考虑:Shadow DOM 的创建和操作可能会影响性能,尤其是在大量组件的情况下。

总结

Shadow DOM 是 Web Components 中实现封装性的关键技术,它通过创建隔离的 DOM 树,使得组件可以拥有自己的样式和行为,而不会影响到页面的其他部分。这使得开发者可以创建更加模块化、可复用的组件,提高代码的可维护性和可扩展性。

基于 VitePress 的本地知识库