Shadow DOM 中事件穿透需同时设置 bubbles: true 和 composed: true;默认 composed: false 阻断穿透,原生事件无法修改该属性,监听位置决定能否捕获事件。

在 Shadow DOM 中,事件冒泡的阻断与穿透不是靠“阻止冒泡”实现的,而是由两个独立参数协同控制:一个是 bubbles(决定是否在 shadow 树内向上冒泡),另一个是 composed(决定是否能穿出 shadow boundary)。两者必须按需组合,缺一不可。
默认行为:事件天然被 Shadow DOM 阻断
Shadow DOM 默认将事件封装在内部。即使你 dispatch 一个 bubbles: true 的自定义事件,它最多冒泡到 shadowRoot 顶层就停止——不会自动抵达宿主元素(host)或外部 DOM。这是因为原生行为中,所有事件默认 composed: false,相当于给 shadow 边界加了一道“单向门禁”。
- 原生事件(如 click、input)无法修改其
composed属性,天生不能穿透 - 自定义事件若未显式设
composed: true,无论bubbles是否为 true,都出不去 -
shadowRoot.querySelector(...).click()触发的原生 click,仍只在 shadow 内传播,外部监听不到
让事件穿透:必须同时设 bubbles 和 composed
要让自定义事件真正到达 light DOM 或 document,必须满足两个条件:
-
bubbles: true→ 保证事件能从子节点一路冒泡到shadowRoot -
composed: true→ 允许事件从shadowRoot“重定向”出去,进入宿主元素及其祖先链
例如:
立即学习“Java免费学习笔记(深入)”;
this.shadowRoot.querySelector('button').dispatchEvent(new CustomEvent('submit', {
bubbles: true,
composed: true,
detail: { data: 'ok' }
})
);
此时,host.addEventListener('submit', ...) 和 document.addEventListener('submit', ...) 都能收到该事件。
主动阻断穿透:显式设 composed: false
即使事件在 shadow 内部正常冒泡,只要设 composed: false(这是默认值),它就不会离开 shadow boundary。这是最稳妥的隔离方式:
- 广告组件、第三方插件等敏感模块,应在所有 dispatch 前强制设置
composed: false - 配合
{ mode: 'closed' }创建 shadow root,可进一步防止外部通过el.shadowRoot反向操作 - 按钮点击后想避免误触外层表单提交?用
button.dispatchEvent(new Event('click', { bubbles: true, composed: false }))
监听位置决定能否捕获:别挂错地方
事件能不能被监听到,取决于你把 addEventListener 挂在哪,以及事件是否 composed:
- 挂在
shadowRoot上 → 只收内部事件,composed值无影响 - 挂在宿主元素(host)上 → 能收到
composed: true的事件,也能收到直接 dispatch 到 host 的事件(不依赖冒泡) - 挂在
document或 light DOM 父级 → 仅当事件composed: true且bubbles: true才能到达


















