CSS伪元素::slotted()在Web Components中的作用域解析

3次阅读

::slotted()仅匹配slot分发的直接子节点,不穿透shadow dom,不作用于slot自身或fallback;需显式分发且初始即确定,动态添加内容不触发重匹配。

CSS伪元素::slotted()在Web Components中的作用域解析

::slotted() 只能选择 slot 中的直接子节点

它不穿透 Shadow DOM 边界,也不能匹配 slot 内部嵌套的后代元素。比如 <my-card><p>Hello <em>world</em></p></my-card>::slotted(p) 能命中 <p></p>,但 ::slotted(em) 无效——因为 <em></em> 并不在 slot 的“顶层”。

  • 只对被 <slot></slot> 实际分发(distributed)的节点生效,不是所有 light DOM 子节点都自动可选
  • 若使用 <slot name="xxx"></slot>,必须用 ::slotted([name="xxx"]) 或对应选择器,不能靠结构推断
  • chrome 96+ 支持 ::slotted(*:not(.hidden)) 这类带伪类的写法,但 safari 16.4 前不支持,慎用复杂条件

::slotted() 无法设置 slot 自身或 fallback 内容样式

很多人误以为给 <slot></slot> 标签加 class 就能用 ::slotted(.my-class) 匹配到它自己——不行。::slotted() 的目标永远是“被插入 slot 的那些外部节点”,不是 slot 元素本身,也不是 slot 的 fallback(即 <slot>fallback text</slot> 中的文字)。

  • 想控制 fallback 文字样式?只能在 slot 外层包一层 <span></span>,再用普通 css 选中
  • <slot class="s1"></slot> 上的 class 对 ::slotted() 完全无意义
  • 如果 slot 没有接收到任何内容,::slotted(*) 不会匹配任何东西,也不会触发 fallback 渲染逻辑

与 :host、:host-context 的作用域边界必须分清

::slotted() 是唯一能从 shadow root 内部“反向影响” light DOM 节点样式的伪元素,但它和 :host 完全不同:后者作用于自定义元素自身,前者只作用于它的子内容。三者共存时容易混淆优先级和层级。

立即学习前端免费学习笔记(深入)”;

  • :host ::slotted(p)::slotted(p) 效果一样,因为 ::slotted() 必须写在 shadow root 内,天然受 :host 作用域约束
  • :host-context(.dark) ::slotted(p) 有效,但前提是 light DOM 中该自定义元素的**祖先**有 .dark 类——不是 slot 内容的祖先
  • 若同时写了 ::slotted(p) { color: red; } 和 light DOM 中的 p { color: blue; },后者会胜出(light DOM 样式优先级更高),这是常被忽略的覆盖失效原因

动态 slot 分发后 ::slotted() 不会重计算样式

js 修改了 slot 内容(比如用 appendChild() 动态加节点),浏览器不会重新触发 ::slotted() 的匹配逻辑——它只在初始分发时确定一次哪些节点被分发到了哪个 slot。

  • 新增节点若没被显式 assign 到某个 <slot></slot>,即使结构上“看起来”在 slot 里,也不会被 ::slotted() 选中
  • slot.setAttribute('name', 'xxx') 切换 slot 分发目标,不会导致已渲染的样式刷新;需手动强制重绘(如 toggle class)或依赖属性变更触发更新
  • 服务端渲染(SSR)或 hydrate 场景下,若初始没有分发内容,::slotted() 规则实际不会应用,JS 补内容后样式可能错位

事情说清了就结束。真正在意样式穿透的人,往往卡在“为什么我改了内容,样式却没变”——答案通常不在 CSS 写法,而在 slot 分发是否真的发生了。

text=ZqhQzanResources