shadow dom 事件重定向是设计机制而非 bug,旨在保护封装性:内部事件跨 shadow boundary 时自动将 event.target 替换为宿主元素;外部应使用 event.composedpath()[0] 获取真实触发源,并通过 composed: true 的自定义事件实现跨边界通信。

Shadow DOM 的事件重定向不是 bug,而是设计机制——它让内部事件“看起来”由宿主元素触发,从而保护封装性。理解这一点,关键在于区分“真实触发源”和“对外暴露的目标”。
事件重定向的本质是目标替换
当 Shadow DOM 内部的 <button></button> 被点击,原生 click 事件会正常冒泡,但在穿过 shadow boundary 的瞬间,浏览器自动把 event.target 改成宿主元素(比如 <my-button></my-button>),而不是那个真实的 <button></button>。这是强制行为,无法禁用,目的是防止外部代码依赖影子树内部结构。
-
内部监听:在 shadow root 里绑定 click,
event.target是真实的按钮 -
外部监听:在 document 或宿主父级监听,
event.target总是宿主元素 - 即使使用
event.stopPropagation(),也阻止不了重定向本身,只影响后续冒泡路径
怎么拿到真正的事件源头
别依赖 event.target 做判断,改用 event.composedPath()[0]——它始终返回事件最初触发的那个节点,无论是否跨 shadow boundary。
如果你了解HTML,CSS和JavaScript,您已经拥有所需的工具开发Android应用程序。本动手本书展示了如何使用这些开源web标准设计和建造,可适应任何Android设备的应用程序 - 无需使用Java。您将学习如何创建一个在您选择的平台的Android友好的网络应用程序,然后转换与自由PhoneGap框架到一个原生的Android应用程序。了解为什么设备无关的移动应用是未来的潮流,并开始构建应用程序,提供更
-
composedPath()返回一个数组,从最深触发点开始,按冒泡顺序列出所有经过的节点 -
composedPath()[0]就是原始目标,composedPath().length > 1说明事件穿过了至少一个 shadow boundary - 例如:
if (event.composedPath()[0].matches('.delete-btn')) { ... }可靠匹配内部按钮
跨边界通信不能靠原生事件穿透
原生事件(click、input、change)默认 composed: false,且该属性只读。想让外部收到“来自 Shadow 内部”的语义化事件,必须主动派发自定义事件,并显式设置 composed: true。
- 在 shadow 内部:
button.dispatchEvent(new CustomEvent('submit', { bubbles: true, composed: true })) - 外部监听:
host.addEventListener('submit', handler)或document.addEventListener('submit', handler)都能收到 - 漏掉
composed: true,事件就停在 shadow boundary,外部完全无感知
slot 投影内容也受重定向约束
通过 <slot></slot> 插入的外部节点,视觉上在组件内部,但事件仍遵循 Shadow DOM 规则:点击 slot 中的内容,外部监听到的 event.target 仍是宿主元素,不是那个投影进来的按钮或输入框。
- 投影内容的事件不会绕过重定向,它只是“显示在” shadow root 里,逻辑上仍属 light DOM
- 若需响应 slot 内部操作,推荐在宿主组件内监听 slot 变化,再由组件主动转发语义事件
- 避免直接对 slot 子节点绑定事件并期望外部捕获——路径不可靠










