Web Components ⟩ Shadow DOM ⟩ Events ⟩
The events that do cross the shadow boundary are:
blur, focus, focusin, focusout
click, dblclick, mousedown, mouseenter, mousemove, ...
compositionstart, compositionupdate, compositionend
dragstart, drag, dragend, drop, ...
Built-in Events
Most (built-in ) events successfully bubble through a shadow DOM boundary (with event.composed == true ).
There are few events that do not:
mouseenter
, mouseleave
(they do not bubble at all),
load
, unload
, abort
, error
These events can be caught only on elements within the same DOM , where the event target resides.
Custom Events
如果要讓 CustomEvent 跨過 shadow DOM boundary,必須要設定:
{bubbles: true, composed: true} ⭐️
If composed: false (default), consumers won't be able to listen for the event outside of your shadow root.
1️⃣ js helpers html css
Copy // attach shadow root (⭐️ so outer's light DOM is not shown)
outer .attachShadow ({mode : 'open' });
let inner = document .createElement ( 'div' );
outer . shadowRoot .append (inner);
/*
div#outer
#shadow-dom
div#inner
*/
// ⭐️ listen for 'test' event
document .addEventListener ( 'test' , e => log (lightDOM , e .detail));
outer . shadowRoot .addEventListener ( 'test' , e => log (shadowDOM , e .detail));
// ⭐️ composed custom event
function sendComposedEvent (){
inner .dispatchEvent ( new CustomEvent ( 'test' , {
bubbles : true ,
composed : true , // ⭐️ composed
detail : "'composed' event from inner <div>"
}));
}
// ⭐️ not composed custom event
function sendNonComposedEvent (){
inner .dispatchEvent ( new CustomEvent ( 'test' , {
bubbles : true ,
composed : false , // ⭐️ not composed
detail : "not composed"
}));
}
Copy // ⭐️ escape HTML special characters
String . prototype . escapeHTML = function () {
return this
.replace ( /&/ g , '&' ) // ampersand (must be first❗️)
.replace ( />/ g , '>' ) // greater than
.replace ( /</ g , '<' ) // less than
.replace ( /"/ g , '"' ) // double-quote
.replace ( /'/ g , ''' ) // single-quote
.replace ( /`/ g , '`' ); // backtick
}
// log
function log (dom , msg) {
dom .innerHTML += msg .escapeHTML () + '<br>' ;
}
Copy <!-- ⭐️ element with shadow DOM -->
<div id="outer">outer div</div>
<div id="buttons">
<button onclick="sendComposedEvent()">composed event</button>
<button onclick="sendNonComposedEvent()">non-composed event</button>
</div>
<!-- console log -->
<div id="lightDOM" class="output">
<p>Light DOM event logs:</p>
</div>
<div id="shadowDOM" class="output">
<p>Shadow DOM event logs:</p>
</div>
Copy .output {
border : 1 px solid black ;
padding : 8 px ;
background : black ;
color : white ;
}
.output p {
margin : -8 px ;
margin-bottom : 8 px ;
padding : 8 px ;
background : tomato ;
}
#lightDOM p {
background : hsl (225 , 80 % , 60 % ) ;
}
#buttons {
margin-bottom : 8 px ;
}
In case of nested components , one shadow DOM may be nested into another. In that case composed events bubble through all shadow DOM boundaries . So, if an event is intended only for the immediate enclosing component , we can:
dispatch it on the shadow host
Then it’s out of the component shadow DOM, but won’t bubble up to higher-level DOM.