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

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
,可选值为open
和closed
。open
表示外部JavaScript可以通过Element.shadowRoot
访问Shadow DOM;closed
表示外部JavaScript无法访问Shadow DOM,Element.shadowRoot
为null
。
- 使用
示例代码:
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
属性为open
或closed
,来定义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 变量的支持,还可以在保持样式隔离的前提下实现跨模块的主题一致性。