Shadow DOM 是唯一真正隔绝样式作用域的机制,它通过浏览器原生切断跨 boundary 样式匹配,而非依赖类名前缀或哈希;必须用 open 模式、显式注入 style/script,并谨慎使用 :host 和 ::slotted 透出通道。

Shadow DOM 不是“给样式加命名空间”,而是直接切断样式作用域——它不解决命名冲突,而是让冲突根本不会发生。
为什么 BEM、CSS Modules 都不是真正的命名空间
所谓“命名空间”方案(如 .my-modal__button)本质是靠人工约定规避重名,但完全挡不住宿主页面的 button { color: red !important; }。CSS 权重和 !important 不看类名前缀,只看选择器类型和数量。一旦宿主写了标签选择器或高特异性规则,你的组件按钮照样变红、边框照样被强制加上。
-
button:focus { outline: 2px solid #000 !important; }会穿透所有类名前缀,包括.ui-kit-button - Less 嵌套生成的
.ui-kit .modal button仍属于全局 CSSOM,宿主样式可按层叠规则覆盖 - Vue 的
scoped或 React 的 CSS Modules 仅做 class 名哈希,不阻断选择器匹配过程
attachShadow({ mode: 'open' }) 是唯一生效的“隔离开关”
调用 attachShadow() 后,浏览器在样式计算阶段就跳过跨 boundary 匹配:宿主的 button 规则根本不会被尝试应用于 shadow 内部的 button 元素,无论它多强、有没有 !important。
- 必须用
{ mode: 'open' },'closed'模式会让shadowRoot不可访问,调试和测试全瘫痪 - 不能把整段 HTML 字符串塞进
shadowRoot.innerHTML——<style></style>标签会被当作文本解析,样式不生效 - 内部样式必须显式创建
<style></style>元素并appendChild到 shadow root,或使用adoptedStyleSheets(Chrome 73+/Safari 16.4+ 支持)
:host 和 ::slotted 是唯一可控的“透出通道”
Shadow DOM 默认完全封闭,但设计了两个受控出口::host 让你响应宿主元素上的 class 或属性,::slotted 让你有限制地影响传入 slot 的内容。
-
:host(.theme-dark) button { background: #333; }只在宿主有theme-dark类时生效 -
::slotted(p) { color: inherit; font-family: var(--font-ui); }仅影响 slot 中顶层<p></p>的可继承属性 - 禁止写
::slotted(*)或:host ::slotted(*)—— 行为不可靠,且破坏封装边界
第三方框架(Foundation、Quill)注入必须绑定到 shadow root
不能靠 <link rel="stylesheet"> 加载到 ,那等于把样式放回全局;也不能用 innerHTML 直接插入含 <script></script> 的字符串——脚本会在全局执行,查不到 shadow 内元素。
- 用
DOMParser解析 HTML 字符串,提取<style></style>文本并创建<style></style>元素插入 shadow root -
<script></script>要新建script元素,appendChild到 shadow root,并确保初始化逻辑传入shadowRoot作为查找上下文 - 相对路径(如
url(./icon.svg))、@import、CSS 变量需提前内联或代理,否则加载失败或继承断裂
真正麻烦的不是怎么写 attachShadow,而是处理可继承属性(color、font-family)的意外穿透,以及第三方库对全局 document 的隐式依赖——这些地方不改代码,光靠 Shadow DOM 挡不住。

















