shadow dom 通过浏览器原生封装机制隔离样式,内部样式默认不泄露到宿主页面,仅 :host 和 ::slotted() 可跨边界有限作用,其他所有选择器均严格限定在 shadow 内部生效。

Shadow DOM 能避免样式泄漏,靠的是浏览器原生的封装机制,不是靠权重、命名或技巧——它从渲染引擎层面就切断了样式的作用范围。
Shadow DOM 内部样式默认不泄露到宿主页面
浏览器在解析样式时,会严格区分“light DOM”(宿主页面的普通 DOM)和“shadow tree”(影子树)。shadowRoot 里的 <style></style> 或 <link rel="stylesheet"> 只参与该 shadow tree 内部节点的样式计算,压根不会被应用到 light DOM 的任何元素上。
比如你在 shadow 中写:
<style>
button { background: green; color: white; }
.card { border: 2px solid #333; }
</style><button>内部按钮</button>
<div class="card">内部卡片</div>
宿主页面里所有 button 和 .card 元素完全不受影响——哪怕它们也用了相同类名或标签,样式也不会传播过去。
真正起作用的是作用域边界,不是选择器强度
-
!important、高特异性选择器(如body div#app ul li a:hover)在 shadow 外依然无效,因为样式规则根本没进入 shadow 内部的匹配流程; -
* { margin: 0 }这类全局重置也不会波及 shadow 内部,浏览器在计算 shadow 内元素样式时,直接跳过 document.styleSheets 里的所有规则; - 即使你用
document.head.appendChild(style)把同一份 CSS 插入全局,它也只影响 light DOM,对 shadow 内部毫无作用。
唯一能跨边界的样式出口是 :host 和 ::slotted()
-
:host允许你为承载 shadow 的宿主元素(即调用attachShadow()的那个元素)设置样式,但仅限于该宿主自身,不影响其兄弟或祖先; -
::slotted()只能作用于通过<slot></slot>投影进来的 light DOM 内容,且只能设置继承性属性(如color、font-size),不能改布局或盒模型; - 其他所有选择器(包括
:host *、:host ::before、.my-class button)都严格限定在 shadow 内部生效,无法外溢。
别指望 scoped、BEM 或 !important 替代 Shadow DOM
-
<style scoped></style>是 Vue 单文件组件的编译产物,本质仍是全局 style 标签 + 属性选择器模拟,无法阻止* { box-sizing: border-box }这类通配符穿透; - BEM 等命名规范只能降低冲突概率,不能杜绝;一旦宿主页面定义了
.btn--primary,你的组件照样被覆盖; - 所有深度选择器(如
/deep/、::ng-deep、>>>)已被现代浏览器废弃,强行使用等于放弃兼容性和可维护性。
所以,要确保样式不泄漏,核心就一条:把 HTML 结构和对应 CSS 全部放进 shadowRoot,且不通过任何方式把 <style></style> 或 <link> 注入到 document.head。











