shadow dom 隔离样式作用域和 dom 查询边界,而非 html 结构;innerhtml 直接赋值失效,须用 domparser 解析后手动注入节点并处理样式。

Shadow DOM 不是用来“隔离 HTML 文档结构”的工具,它隔离的是样式作用域和 DOM 查询边界——结构本身仍可被 JavaScript 遍历(除非用 closed 模式),但外部 CSS 和 document.querySelector 默认看不到 shadow 内部节点。
直接赋值 innerHTML = htmlString 到 shadowRoot 是危险且失效的:
-
<style></style>被当作文本节点解析,不进 CSSOM,后续无法动态更新 -
<script></script>在全局执行,可能污染window或触发 CSP 报错 -
<link rel="stylesheet">仍加载到主文档,完全失去隔离意义
真正起效的,是浏览器在样式计算阶段就跳过跨 boundary 匹配。这不是优先级问题,是规则压根不被送入 shadowRoot 的 CSSOM 构建流程。
为什么不能用 innerHTML 注入第三方 HTML
现象包括:宿主页面字体突变、按钮边距消失、document.querySelector('.ql-toolbar') 返回 null 但编辑器渲染异常。
根本原因不是语法错,而是 ShadowRoot 不是容器,是边界——它只接受标准 DOM 节点,不解析字符串中的标签语义。
-
innerHTML会把<style></style>当普通文本,无法参与样式计算 -
<script></script>执行时this和document仍是全局,无法限定作用域 -
<link>加载后写入主文档,第三方 CSS 泄漏到整个页面
正确注入 HTML 字符串的链路
必须用 DOMParser 解析后手动处理各节点,而不是拼字符串。
- 调用
new DOMParser().parseFromString(html, 'text/html') - 遍历
doc.body.childNodes,对每个Node.ELEMENT_NODE做分类处理 -
<style></style>节点:创建新style元素,设textContent,再shadowRoot.appendChild(style) -
<link rel="stylesheet">:提取href,用fetch加载内容,转为CSSStyleSheet后通过adoptedStyleSheets注入(注意 Safari 16.4+ / Chrome 73+ 支持) -
<script></script>:剥离或重写执行上下文(如绑定shadowRoot为document),否则禁止执行
宿主元素选错或 attachShadow() 调用时机错误
attachShadow() 会直接抛 InvalidStateError 或返回 null,常见原因:
- 宿主元素类型不支持:
<div>、<code><span></span>、自定义元素(如<cms-block></cms-block>)可以;<p></p>、<img>、<input>等原生替换元素一律不行 - 模式设为
'closed':导致host.shadowRoot恒为null,DevTools 无法 inspect,调试等于盲操 - 宿主未插入 DOM:不能在
document.createElement('div')后立刻调用,必须等connectedCallback或已挂载到文档树 -
:host([disabled])匹配宿主自身属性,可用于响应状态变化 -
:host-context(.dark)匹配祖先链上的 class,适合主题透传;:host(.dark)是常见误用,它只查宿主自己有没有该 class -
::slotted(p)只作用于被<slot></slot>投影进来的顶层p,且仅支持可继承属性(color、font、background-color) - 禁止写
::slotted(*)或:host ::slotted(*)——行为不一致,也违背封装初衷;slot 内容的布局责任本该由使用者承担
:host 和 ::slotted() 是唯一合法穿透通道
W3C 规范只允许这两个伪类跨 boundary,其余全是单向封闭。它们不是“加强版选择器”,而是边界协议接口。
复杂点在于:CSS 变量继承链在 shadow boundary 处断裂,@import 和相对路径(如 url(./img.png))需提前做资源代理或内联替换;adoptedStyleSheets 在旧 Safari 中不稳定,fallback 方案仍需手动生成 style 标签并注入。











