核心概念
Shadow DOM 是 Web 组件标准的一部分,它主要解决了一个问题:将一个组件的 HTML 结构、CSS 样式与主文档隔离开来,使其不会受到外部样式的影响,也不会影响到外部文档。
你可以把它想象成一个**“样式作用域”或“封装罩”**。
解决的问题
在没有 Shadow DOM 的时代,我们经常会遇到这样的麻烦:
- 样式冲突:你写了一个复杂的组件(例如一个标签页组件),用了类名
.tab和.active。但如果页面其他地方也有一个同名的 CSS 类,你的组件样式就会被意外修改,页面变得一团糟。 - 结构暴露:你的组件 HTML 结构完全暴露在全局文档中,如果其他脚本不小心用
document.querySelector选择了你组件内部的元素,可能会引发难以调试的 Bug。 - 缺乏封装性:很难编写一个可以“即插即用”、自包含的独立组件。
Shadow DOM 就是为了实现真正的“封装”而生的。
Shadow DOM 的关键术语和关系
要理解 Shadow DOM,需要先了解几个关键概念:
- Light DOM: 这是指我们平常写的、在全局文档流中的普通 DOM 节点。也就是“外部”。
- Shadow Host: 一个普通的 DOM 节点,它是 Shadow DOM 的“挂载点”或“容器”。
- Shadow Tree: 挂在 Shadow Host 内部的、被封装起来的 DOM 树。这就是 Shadow DOM 本身。
- Shadow Root: Shadow Tree 的根节点。我们可以通过它来操作 Shadow Tree 内部的内容。
它们的关系可以用一个简单的比喻来理解:
- Shadow Host 就像一个带插座的电源墙插。
- Shadow Root 就是墙插上的插座口。
- Shadow Tree 就是插在插座上的一个黑盒子电器(比如电视机)。
- 墙插(Host)和外部电路(Light DOM)是连通的,但电视机内部(Shadow Tree)的复杂电路和元件是被外壳封装起来的,外部世界无法直接触碰。
如何创建和使用 Shadow DOM
HTML 结构(Light DOM):
<!DOCTYPE html>
<html>
<body>
<!-- 这是一个普通的 div,它将作为 Shadow Host -->
<div id="my-shadow-host"></div>
<script>
// 1. 先找到 Shadow Host
const hostElement = document.getElementById('my-shadow-host');
// 2. 为这个 Host 附加一个 Shadow Root(打开封装模式)
// mode: 'open' 表示可以从外部通过 JavaScript 访问 Shadow Root
// mode: 'closed' 则完全封闭,外部无法访问(很少用)
const shadowRoot = hostElement.attachShadow({ mode: 'open' });
// 3. 向 Shadow Root 内部添加内容和样式(这就是 Shadow Tree)
shadowRoot.innerHTML = `
<style>
/* 这个 p 标签的样式只在这个 Shadow Tree 内部生效! */
p {
color: red;
font-weight: bold;
}
/* 外部的 CSS 无法覆盖这个规则 */
</style>
<p>这段文字在 Shadow DOM 内部,是红色的粗体。</p>
`;
</script>
<!-- 这是 Light DOM 里的一个普通 p 标签,不受上面样式影响 -->
<p>这段文字在 Light DOM 内部,保持默认样式。</p>
</body>
</html>
运行结果: 页面上会显示两段文字:
- 第一段是红色的粗体(在 Shadow DOM 内部)。
- 第二段是普通的默认样式(在 Light DOM 内部)。
即使你在外部 CSS 中写 p { color: blue !important; },也无法将第一段文字变成蓝色! 这就是样式的封装性。
总结
浏览器原生技术,它通过创建独立的 DOM 子树和样式作用域,解决了样式和脚本的全局污染问题
- 隔离的 DOM: Shadow Tree 内部的节点不会在全局的
document.querySelector(‘p’)中被找到。你必须通过 Shadow Root 来查询,例如shadowRoot.querySelector(‘p’)。 - 作用域 CSS: Shadow Tree 内部定义的 CSS 只作用于内部。外部的 CSS 规则(除了少数继承性样式,如
color,font-family,或使用::part等特殊伪类)也无法影响内部。 - 简化 CSS: 因为样式被隔离了,你可以在 Shadow DOM 内部使用非常简单的选择器(如
button,.class),而不用担心和全局样式冲突。
应用场景
Shadow DOM 最主要的应用就是 Web Components。
现代前端框架(如 Vue、React、Angular)在底层也利用了类似的封装思想来构建组件。很多浏览器原生组件也是用 Shadow DOM 实现的,例如:
<video>元素:它内部有复杂的播放/暂停按钮、进度条等控件,但这些结构对你是隐藏的,这就是通过 Shadow DOM 封装的。<input type="range">:滑动条也是用 Shadow DOM 实现的。
你可以打开浏览器的开发者工具,在 Settings > Preferences > Elements 中勾选 “Show user agent shadow DOM”,然后检查 <video> 或 <input type=“range”> 元素,就能看到其内部被隐藏的 Shadow DOM 结构了。