Shadow DOM结构隔离是硬隔离,shadowRoot内节点不在light DOM树中,document.querySelector无法访问;必须显式通过shadowRoot.querySelector获取,事件需设置composed: true才可穿透,:host和::slotted()是W3C唯一允许跨边界样式接口。

Shadow DOM结构隔离不是“查不到”,是根本不在light DOM树里
用document.querySelector('.my-button')查不到影子树内的节点,这不是选择器写错了,是浏览器压根不把 shadowRoot 里的元素纳入 light DOM 的遍历范围。结构隔离是硬隔离:DOM API 默认只操作 light DOM,shadowRoot 是独立子树,必须显式访问。
常见错误现象:
- 调用
el.querySelector('button')返回null,但el.shadowRoot.querySelector('button')能取到 —— 因为没进 shadowRoot 就等于没看见 - 事件监听绑定在父容器上,却收不到 shadow 内部
click—— 需要设置composed: true才能让事件穿透 boundary - 用
innerHTML直接替换宿主元素内容,会清空已 attach 的 shadowRoot —— 结构隔离不防你删它,只防你“误入”
:host 和 ::slotted() 是唯一能跨 boundary 的 CSS 接口,不是“加强版选择器”
它们是 W3C 规范定义的两个合法出口,其余所有穿透方式(::ng-deep、/deep/、>>)已在 Chrome 120+ 和 Firefox 115+ 中被移除。继续用等于放弃现代浏览器兼容性。
关键限制必须清楚:
-
:host只匹配宿主元素自身(即这个标签),不能写:host div.title去选内部子元素 -
:host([disabled])有效,:host-context(.theme-dark)才能响应外部 -
::slotted(p)只作用于被投影进来的 light DOM 节点,且仅支持可继承属性:color、font-family、line-height、background-color -
::slotted(*) { margin: 16px }无效 ——margin不可继承,浏览器直接忽略
样式注入必须进 CSSOM,别用 innerHTML 拼
shadowRoot.innerHTML = '
' 是典型错误:其中的 标签会被当普通 HTML 解析,不进入 CSSOM,后续 JS 修改 sheet.cssRules 完全无效,甚至触发 FOUC。
正确做法只有两种:
- 分步操作:先设结构(
shadowRoot.innerHTML = ''),再用document.createElement('style')+textContent+appendChild() - 使用
CSSStyleSheet+adoptedStyleSheets(Chrome 73+、Safari 16.4+ 支持):shadowRoot.adoptedStyleSheets = [baseStyles],适合复用基础重置或主题样式
CSS 自定义属性能“透入”不是因为穿透,而是靠继承链
--primary-color 看起来像穿透了 shadow boundary,其实只是顺着 DOM 树从宿主元素自然继承下来。它不经过选择器匹配,也不依赖任何伪类,只依赖属性是否挂载在宿主或其祖先上。
所以这些写法效果完全不同:
- ✅
—— 变量挂在宿主上,能进 shadow - ✅
+.theme-dark { --primary: #1e1e1e; }—— 无效,变量没挂宿主,进不去 - ⚠️ 使用时必须带 fallback:
color: var(--primary, #007bff);,否则变量未定义会导致样式崩溃
真正难的从来不是怎么写样式,而是搞清哪条规则在哪棵树里计算、哪个变量从哪一层继承下来——边界不是墙,是协议。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











