微前端样式隔离方案之Shadow DOM

Shadow DOM 介绍

Shadow DOM 是Web Components技术的一部分,浏览器提供了 attachShadow API允许开发者将一个隐藏的、独立的DOM树附加到一个常规DOM元素(称为Shadow Host)上,其内部的样式、结构和脚本与外部完全隔离,就像给元素加了个 "防护罩",彻底解决样式冲突和结构污染问题。

null

Shadow DOM 组成如图所示。

  • Shadow host: Shadow DOM 附加到的常规 DOM 节点。
  • Shadow tree: Shadow DOM 内部的 DOM 树。
  • Shadow boundary: Shadow DOM 终止,常规 DOM 开始的地方。
  • Shadow root: Shadow DOM 的根节点。

基本使用

  • 通过JavaScript创建

    • 使用Element.attachShadow()方法给指定元素挂载Shadow DOM,返回对Shadow Root的引用。
    • 创建时需指定mode,可选值为openclosedopen表示外部JavaScript可以通过Element.shadowRoot访问Shadow DOM;closed表示外部JavaScript无法访问Shadow DOM,Element.shadowRootnull

示例代码:

ini 复制代码
const shadowHost = document.querySelector('.shadow-host');
const shadowRoot = shadowHost.attachShadow({mode: 'open'});
const shadowDiv = document.createElement('div');
shadowDiv.classList.add('shadow-div');
shadowDiv.innerText = 'i am shadow div';
shadowRoot.appendChild(shadowDiv);
  • 通过HTML声明式创建

    • 使用<template>元素,并设置shadowrootmode属性为openclosed,来定义Shadow DOM。

示例代码:

xml 复制代码
<div id="host">
  <template shadowrootmode="open">
    <span>I'm in the shadow DOM</span>
  </template>
</div>

Shadow DOM 规则

可挂载的元素范围

并非所有元素都能挂载 Shadow DOM,出于安全考虑,部分元素被限制。以下是可挂载的常见元素:

  • web components 中的自定义元素
  • 标准块级元素:<article>, <aside>, <blockquote>, <footer>, <header>, <main>, <nav>, <section>
  • 基础容器元素:<body>, <div>, <p>
  • 行内元素:<span>
  • 标题元素:<h1>, <h2>, <h3>, <h4>, <h5>, <h6>

使用不支持的元素进行挂载,浏览器会报错:This element does not support attachShadow

从外部控制 Shadow DOM 内部样式::part

:part 是一个 CSS 伪类,用于从外部样式表中选择 Shadow DOM 内部的元素。它允许开发者在 Shadow DOM 的外部定义样式规则,从而对 Shadow DOM 内部的组件进行样式定制。这种方式打破了 Shadow DOM 的封装性,提供了更灵活的样式定制能力。

示例代码

xml 复制代码
<div id="host">
  <template shadowrootmode="open">
    <style>
      /* Shadow DOM 内部的样式 */
      :host {
        display: block;
        border: 1px solid black;
        padding: 10px;
      }
      .header {
        font-size: 1.2em;
        color: blue;
      }
      .content {
        margin-top: 10px;
      }
    </style>
    <div part="header" class="header">Header</div>
    <div part="content" class="content">Content</div>
  </template>
</div>
css 复制代码
/* 外部样式表 */
#host::part(header) {
  color: red; /* 覆盖 Shadow DOM 内部的 header 样式 */
  font-size: 1.5em;
}

#host::part(content) {
  background-color: yellow; /* 覆盖 Shadow DOM 内部的 content 样式 */
}

以上代码中:

  • 在 Shadow DOM 内部,元素通过 part 属性标记,例如 <div part="header">
  • 在外部样式表中,使用 ::part 选择器来定位这些标记的元素,并覆盖其样式。
  • 通过这种方式,开发者可以在不修改 Shadow DOM 内部代码的情况下,灵活地调整组件的外观。

在 Shadow DOM 内部选择宿主元素::host

:host 是一个伪类选择器,用于在 Shadow DOM 内部选择宿主元素(即 Shadow DOM 的宿主元素)。它允许开发者为宿主元素定义样式,而这些样式不会影响到 Shadow DOM 外部的其他元素。

示例代码

xml 复制代码
<div id="host">
  <template shadowrootmode="open">
    <style>
      /* Shadow DOM 内部的样式 */
      :host {
        display: block;
        border: 1px solid black;
        padding: 10px;
        background-color: lightgray;
      }
      .content {
        margin-top: 10px;
      }
    </style>
    <div class="content">Content</div>
  </template>
</div>

以上代码中:

  • 在 Shadow DOM 内部的 <style> 标签中,使用 :host 选择器定义宿主元素 div 的样式。
  • 这些样式仅适用于当前 Shadow DOM 的宿主元素,不会影响到其他元素。

根据宿主元素的状态设置样式::host-context

:host-context 是一个伪类选择器,用于在 Shadow DOM 内部根据宿主元素的上下文(例如父元素的类或属性)来选择元素。它允许开发者根据宿主元素的外部状态来调整 Shadow DOM 内部的样式。

示例代码

xml 复制代码
<template id="my-component-template">
  <style>
    /* Shadow DOM 内部的样式 */
    :host-context(.dark-theme) {
      background-color: darkgray;
      color: white;
    }
    :host-context(.light-theme) {
      background-color: lightgray;
      color: black;
    }
    .content {
      padding: 10px;
    }
  </style>
  <div class="content">Content</div>
</template>
<script>
  class MyComponent extends HTMLElement {
    constructor() {
      super();
      const shadowRoot = this.attachShadow({ mode: 'open' });
      const template = document.getElementById('my-component-template').content.cloneNode(true);
      shadowRoot.appendChild(template);
    }
  }

  customElements.define('my-component', MyComponent);
</script>
<div class="dark-theme">
  <my-component></my-component>
</div>
<div class="light-theme">
  <my-component></my-component>
</div>

以上代码中:

  • 在 Shadow DOM 内部的 <style> 标签中,使用 :host-context 选择器根据宿主元素的上下文(如父元素的类 .dark-theme.light-theme)来定义样式。
  • 这种方式允许 Shadow DOM 内部的样式根据外部环境动态调整。

插槽样式设置::slotted

:slotted 是一个伪类选择器,用于在 Shadow DOM 内部选择插槽(<slot>)中的内容。它允许开发者为插槽中的内容定义样式,而这些样式不会影响到 Shadow DOM 外部的其他元素。

示例代码

xml 复制代码
<div id="host2">
  <template shadowrootmode="open">
    <style>
      p {
        font-size: 16px;
        color: orange;
        margin: 10px;
      }

      ::slotted(span) {
        color: red;
        font-weight: bold;
      }
    </style>
    <p>i am in shadow</p>
    <slot name="span"></slot>
  </template>
  <!-- slot 内容 -->
  <span id="s1" slot="span">i am slot in shadow</span>
</div>

以上代码中:

  • 在 Shadow DOM 内部的 <style> 标签中,使用 :slotted 选择器为插槽中的内容(如 <span>)定义样式。
  • 这些样式仅适用于插槽中的内容,而不会影响到 Shadow DOM 外部的其他元素。

Host 样式穿透

在 Shadow DOM 中,如果 host 使用的是 HTML 元素,例如 div 等。宿主元素(:host)的某些样式属性可以被其内部的 Shadow DOM 元素继承。这种继承主要取决于 CSS 的继承机制以及 Shadow DOM 的封装特性。可继承的属性主要包含:

属性类别 属性名称
字体样式 font-family
font-size
font-weight
font-style
line-height
文本样式 color
text-align
text-decoration
text-transform
white-space
word-spacing
letter-spacing
direction
unicode-bidi
其他 cursor
visibility
pointer-events

CSS 变量

CSS 变量(自定义属性)是少数可以穿透 Shadow DOM 边界的技术之一。外部定义的 CSS 变量可以在 Shadow DOM 内部使用。

例如:

css 复制代码
/* 外部样式 */
:root {
  --primary-color: blue;
}
xml 复制代码
const shadow = element.attachShadow({ mode: 'open' });
shadow.innerHTML = `
  <style>
    p { color: var(--primary-color); }
  </style>
  <p>Hello, Shadow DOM!</p>
`;

在这种情况下,<p> 元素会使用外部定义的 --primary-color 变量,显示为蓝色。

由于 CSS 变量的作用域是全局的,你可以在外部动态更新它们的值,这些变化会立即反映到 Shadow DOM 内部。

例如:

javascript 复制代码
document.documentElement.style.setProperty('--primary-color', 'green');

更新后,Shadow DOM 内部的 <p> 元素的颜色会变为绿色。

兼容性

attachShadow 在主流浏览器中都已经得到了支持,IE 是一贯的不兼容。主要是移动端兼容情况还不明了,特别是国内各家手机厂商生态太过丰富,百花齐放。

总结

Shadow DOM 通过严格的样式封装,确保内部样式仅在自身作用域内生效,有效避免了全局样式冲突,使组件样式更加可控和可预测。此外,Shadow DOM 原生支持 CSS 变量,为开发者提供了高度的灵活性。利用 CSS 变量可以统一管理主题颜色、字体大小等全局样式参数,或者针对不同场景定制组件的外观和行为。

在微前端架构中,Shadow DOM 的优势尤为突出。微前端通常涉及多个独立开发的子应用或模块,它们可能由不同团队维护,使用不同的技术栈。Shadow DOM 的封装特性天然适合解决样式隔离问题,确保每个子应用的样式互不干扰。同时,通过 CSS 变量的支持,还可以在保持样式隔离的前提下实现跨模块的主题一致性。

相关推荐
该换个名儿了6 分钟前
git多个commit合并成一个
前端·git
LaoZhangAI8 分钟前
2025最新OpenAI组织验证失败完全解决方案:8种有效方法彻底修复【实战指南】
前端·后端
siwangqishiq220 分钟前
Vulkan Tutorial 教程翻译(三) 绘制三角形 2.1 安装
前端
LaughingZhu21 分钟前
PH热榜 | 2025-06-05
前端·人工智能·经验分享·搜索引擎·产品运营
大模型真好玩21 分钟前
最强大模型评测工具EvalScope——模型好不好我自己说了算!
前端·人工智能·python
Dream耀37 分钟前
CSS选择器完全手册:精准控制网页样式的艺术
前端·css·html
wordbaby37 分钟前
React 19 亮点:让异步请求和数据变更也能用 Transition 管理!
前端·react.js
月亮慢慢圆38 分钟前
VUE3基础之Hooks
前端
我想说一句39 分钟前
CSS 基础知识小课堂:从“选择器”到“声明块”,带你玩转网页的时尚穿搭!
前端·javascript·面试