Shadow DOM中插槽内容事件冒泡不经过Shadow内部节点,target被重定向为宿主元素;原生事件默认不跨boundary,自定义事件需设置composed:true才能穿透。

Shadow DOM 对插槽(<slot>)内容的事件冒泡行为有明确且统一的规则:**插槽内容本身属于 Light DOM,其事件不经过 Shadow DOM 内部节点,但冒泡路径仍受 Shadow boundary 约束;外部监听到的 event.target 会被重定向为宿主元素,而非原始触发节点。**
插槽内容的事件始终走 Light DOM 路径
通过 <slot> 投影进来的节点(比如 <my-card><button>提交</button></my-card> 中的 <button>),物理上仍保留在 Light DOM 中,它的事件冒泡路径是:
- 从该节点开始 → 宿主元素(
<my-card>)→ 宿主的父级 Light DOM 节点 - 全程不经过 Shadow DOM 内部任何节点(如
<slot>自身或其兄弟元素) - 也就是说,即使按钮在视觉上“显示在组件内部”,它点击时不会触发 Shadow 内部
<slot>上的监听器,也不会经过<div class="inner">这类影子树节点
event.target 总是被重定向为宿主元素
这是 Shadow DOM 的封装机制,不是 bug:
- 无论事件来自 Shadow 内部元素,还是来自 slot 投影的内容,只要冒泡出 Shadow boundary,外部看到的
event.target都是宿主元素(例如<my-card>) - 目的是防止外部逻辑依赖组件内部结构或投射内容的具体位置
- 想获取真实触发节点,请用
event.composedPath()[0]—— 它在外部也能正确返回原始的<button>
原生事件无法穿透,自定义事件需手动配置
插槽内容触发的原生事件(如 click、input)默认 composed: false,因此:
- 它们能正常冒泡到宿主,但不会继续向上传播到宿主的 Light DOM 父级(除非宿主自己再派发)
- 想让外部(比如
document)直接响应插槽内的点击,必须由宿主内部主动派发自定义事件: this.dispatchEvent(new CustomEvent('slot-click', { bubbles: true, composed: true, detail: { target: event.target } }));- 漏掉
composed: true,事件就停在宿主,外部收不到
slot 元素自身不捕获也不中转事件
<slot> 是一个分发占位符,不是渲染容器:
- 它不参与事件流 —— 你不能给
<slot name="main">绑定click监听器来捕获投射内容的点击 - 投射内容的事件监听器照常执行(比如你在 slot 外部传入的
<button onclick="alert(1)">仍会弹窗),但冒泡路径止于宿主 - 若需统一处理所有投射内容事件,应在宿主的
connectedCallback或slotchange后,遍历slot.assignedNodes()并委托监听


















