Declarative Shadow DOM 封装

FreeGuideOnline 最新 2026-07-13

这段文字在 Shadow DOM 内部,颜色为蓝色。

这段文字作为 Light DOM 内容,会被插入到 slot 中。

```
  • shadowrootmode 取值:
    • "open":影子根开放,外部可通过 element.shadowRoot 访问。
    • "closed":影子根关闭,外部无法获取(较少使用)。
  • <template> 元素会被浏览器替换为实际的 Shadow Root,其内部 DOM 成为封装内容。

封装效果演示

1. 样式隔离

声明式 Shadow DOM 内部的样式仅影响影子树,不会泄漏到外部,外部样式也无法穿透。

<div>
  <style>p { color: red; }</style>
  <p>这是红色的外部段落。</p>
  <user-card>
    <template shadowrootmode="open">
      <style>p { color: green; font-weight: bold; }</style>
      <p>这张卡片内的文字为绿色加粗。</p>
    </template>
  </user-card>
</div>

上述代码中,外部的红色规则不会影响 <user-card> 内部的 <p>,内部的绿色规则也不会影响外部段落。

2. 插槽(Slot)投射

使用 <slot> 元素将 Light DOM 内容投射到影子树指定位置。

<my-panel>
  <template shadowrootmode="open">
    <h2>面板标题</h2>
    <slot>默认内容,当没有传入任何内容时显示。</slot>
  </template>
  <p>这是插入的 Light DOM 段落。</p>
</my-panel>

在上例中,Light DOM 中的 <p> 会替换影子树内的 <slot> 元素。如果 <my-panel> 内部没有任何内容,则会显示“默认内容...”。

与自定义元素配合使用

Declarative Shadow DOM 常与自定义元素(Custom Elements)结合,实现完全封装的组件:

<my-greeting name="Alice">
  <template shadowrootmode="open">
    <style>
      :host { display: block; border: 1px solid #ccc; padding: 1em; }
      span { color: #0077cc; }
    </style>
    <p>你好,<span id="name"></span>!</p>
  </template>
</my-greeting>

<script>
  class MyGreeting extends HTMLElement {
    constructor() {
      super();
      // 如果模板尚未被解析为 Shadow Root,则手动附加
      // 但实际上,当 HTML 中存在 template[shadowrootmode] 时,浏览器已自动创建 Shadow Root
    }
    connectedCallback() {
      const name = this.getAttribute('name');
      const span = this.shadowRoot?.querySelector('#name');
      if (span) span.textContent = name;
    }
  }
  customElements.define('my-greeting', MyGreeting);
</script>

注意:在声明式 Shadow DOM 中,浏览器会自动将 <template shadowrootmode="open"> 转换为 Shadow Root 附加到父元素上。因此自定义元素的构造函数中无需再调用 attachShadow()。使用 this.shadowRoot 可直接获取影子根。

服务端渲染 (SSR) 支持

声明式 Shadow DOM 的最大优势之一是服务端可以预先输出完整的 DOM 结构:

<!-- 服务端输出的 HTML -->
<user-profile>
  <template shadowrootmode="open">
    <style>
      :host { display: flex; align-items: center; }
      .avatar { width: 40px; height: 40px; border-radius: 50%; background: #ddd; }
    </style>
    <div class="avatar"></div>
    <span>张三</span>
  </template>
</user-profile>

浏览器解析后立即呈现封装好的组件,无需等待任何 JavaScript。之后,可以通过 Hydration (水合作用)绑定交互逻辑,例如添加事件监听器,实现完全功能的组件。

使用场景与最佳实践

  • 静态内容卡片:不依赖复杂 JS 交互的展示型组件(如文章预览、用户头像)。
  • 渐进增强:先通过声明式 Shadow DOM 提供基础样式隔离,再通过 JS 添加交互。
  • SEO 优化:服务端直接输出带 shadow 结构的内容,搜索引擎可以更好地抓取。
  • 混合模式:在支持自定义元素的框架中,利用声明式 Shadow DOM 作为模板语法,减少运行时的模板编译开销。

浏览器兼容性

Declarative Shadow DOM 已在现代浏览器中得到支持:

  • Chrome / Edge 90+
  • Safari 16.4+
  • Firefox 123+(实验性支持)

对于不支持的浏览器,可以使用 polyfill 或者保持命令式方式作为降级方案。

完整示例:创建一个提示框组件

下面是一个完整的声明式 Shadow DOM 组件示例,展示如何封装样式、使用插槽和属性。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>声明式 Shadow DOM 示例</title>
</head>
<body>
  <info-banner type="info">
    <template shadowrootmode="open">
      <style>
        :host { display: block; padding: 1em; margin: 1em 0; border-radius: 4px; }
        :host([type="info"])  { background: #e7f3ff; color: #004085; }
        :host([type="warn"])  { background: #fff3cd; color: #856404; }
        :host([type="error"]) { background: #f8d7da; color: #721c24; }
        .icon { margin-right: 0.5em; font-weight: bold; }
      </style>
      <span class="icon">•</span><slot></slot>
    </template>
    这是一条信息提示。
  </info-banner>

  <info-banner type="warn">
    <template shadowrootmode="open">
      <style>
        :host { display: block; padding: 1em; margin: 1em 0; border-radius: 4px; }
        :host([type="info"])  { background: #e7f3ff; color: #004085; }
        :host([type="warn"])  { background: #fff3cd; color: #856404; }
        :host([type="error"]) { background: #f8d7da; color: #721c24; }
        .icon { margin-right: 0.5em; font-weight: bold; }
      </style>
      <span class="icon">⚠️</span><slot></slot>
    </template>
    请检查输入内容。
  </info-banner>
</body>
</html>