W3Cschool
恭喜您成為首批注冊用戶
獲得88經驗值獎勵
Shadow tree 背后的思想是封裝組件的內部實現(xiàn)細節(jié)。
假設,在 <user-card>
組件的 shadow DOM 內觸發(fā)一個點擊事件。但是主文檔內部的腳本并不了解 shadow DOM 內部,尤其是當組件來自于第三方庫。
所以,為了保持細節(jié)簡單,瀏覽器會重新定位(retarget)事件。
當事件在組件外部捕獲時,shadow DOM 中發(fā)生的事件將會以 host 元素作為目標。
這里有個簡單的例子:
<user-card></user-card>
<script>
customElements.define('user-card', class extends HTMLElement {
connectedCallback() {
this.attachShadow({mode: 'open'});
this.shadowRoot.innerHTML = `<p>
<button>Click me</button>
</p>`;
this.shadowRoot.firstElementChild.onclick =
e => alert("Inner target: " + e.target.tagName);
}
});
document.onclick =
e => alert("Outer target: " + e.target.tagName);
</script>
如果你點擊了 button,就會出現(xiàn)以下信息:
BUTTON
? —— 內部事件處理程序獲取了正確的目標,即 shadow DOM 中的元素。USER-CARD
? —— 文檔事件處理程序以 shadow host 作為目標。事件重定向是一件很棒的事情,因為外部文檔并不需要知道組件的內部情況。從它的角度來看,事件是發(fā)生在 <user-card>
。
如果事件發(fā)生在 slotted 元素上,實際存在于 light DOM 上,則不會發(fā)生重定向。
例如,在下面的例子中,如果用戶點擊了 <span slot="username">
,那么對于 shadow 和 light 處理程序來說,事件目標就是當前這個 span
元素。
<user-card id="userCard">
<span slot="username">John Smith</span>
</user-card>
<script>
customElements.define('user-card', class extends HTMLElement {
connectedCallback() {
this.attachShadow({mode: 'open'});
this.shadowRoot.innerHTML = `<div>
<b>Name:</b> <slot name="username"></slot>
</div>`;
this.shadowRoot.firstElementChild.onclick =
e => alert("Inner target: " + e.target.tagName);
}
});
userCard.onclick = e => alert(`Outer target: ${e.target.tagName}`);
</script>
如果單擊事件發(fā)生在 "John Smith"
上,則對于內部和外部處理程序來說,其目標是 <span slot="username">
。這是 light DOM 中的元素,所以沒有重定向。
另一方面,如果單擊事件發(fā)生在源自 shadow DOM 的元素上,例如,在 <b>Name</b>
上,然后當它冒泡出 shadow DOM 后,其 event.target
將重置為 <user-card>
。
出于事件冒泡的目的,使用扁平 DOM(flattened DOM)。
所以,如果我們有一個 slot 元素,并且事件發(fā)生在它的內部某個地方,那么它就會冒泡到 <slot>
并繼續(xù)向上。
使用 event.composedPath()
獲得原始事件目標的完整路徑以及所有 shadow 元素。正如我們從方法名稱中看到的那樣,該路徑是在組合(composition)之后獲取的。
在上面的例子中,扁平 DOM 是:
<user-card id="userCard">
#shadow-root
<div>
<b>Name:</b>
<slot name="username">
<span slot="username">John Smith</span>
</slot>
</div>
</user-card>
因此,對于 <span slot="username">
上的點擊事件,會調用 event.composedPath()
并返回一個數(shù)組:[span
, slot
, div
, shadow-root
, user-card
, body
, html
, document
, window
]。在組合之后,這正是扁平
DOM 中目標元素的父鏈。
Shadow 樹詳細信息僅提供給 ?
{mode:'open'}
? 樹如果 shadow 樹是用
{mode: 'closed'}
創(chuàng)建的,那么組合路徑就從 host 開始:user-card
及其更上層。
這與使用 shadow DOM 的其他方法的原理類似。closed 樹內部是完全隱藏的。
大多數(shù)事件能成功冒泡到 shadow DOM 邊界。很少有事件不能冒泡到 shadow DOM 邊界。
這由 composed
事件對象屬性控制。如果 composed
是 true
,那么事件就能穿過邊界。否則它僅能在 shadow DOM 內部捕獲。
如果你瀏覽一下 UI 事件規(guī)范 就知道,大部分事件都是 composed: true
:
blur
,focus
,focusin
,focusout
,click
,dblclick
,mousedown
,mouseup
mousemove
,mouseout
,mouseover
,wheel
,beforeinput
,input
,keydown
,keyup
。所有觸摸事件(touch events)及指針事件(pointer events)都是 composed: true
。
但也有些事件是 composed: false
的:
mouseenter
,mouseleave
(它們根本不會冒泡),load
,unload
,abort
,error
,select
,slotchange
。這些事件僅能在事件目標所在的同一 DOM 中的元素上捕獲,
當我們發(fā)送(dispatch)自定義事件,我們需要設置 bubbles
和 composed
屬性都為 true
以使其冒泡并從組件中冒泡出來。
例如,我們在 div#outer
shadow DOM 內部創(chuàng)建 div#inner
并在其上觸發(fā)兩個事件。只有 composed: true
的那個自定義事件才會讓該事件本身冒泡到文檔外面:
<div id="outer"></div>
<script>
outer.attachShadow({mode: 'open'});
let inner = document.createElement('div');
outer.shadowRoot.append(inner);
/*
div(id=outer)
#shadow-dom
div(id=inner)
*/
document.addEventListener('test', event => alert(event.detail));
inner.dispatchEvent(new CustomEvent('test', {
bubbles: true,
composed: true,
detail: "composed"
}));
inner.dispatchEvent(new CustomEvent('test', {
bubbles: true,
composed: false,
detail: "not composed"
}));
</script>
事件僅僅是在它們的 composed
標志設置為 true
的時候才能通過 shadow DOM 邊界。
內建事件大部分都是 composed: true
的,正如相關規(guī)范所描述的那樣:
也有些內建事件它們是 composed: false
的:
mouseenter
,mouseleave
(也不冒泡),load
,unload
,abort
,error
,select
,slotchange
。
這些事件僅能在同一 DOM 中的元素上捕獲。
如果我們發(fā)送一個 CustomEvent
,那么我們應該顯式地設置 composed: true
。
請注意,如果是嵌套組件,一個 shadow DOM 可能嵌套到另外一個 shadow DOM 中。在這種情況下合成事件冒泡到所有 shadow DOM 邊界。因此,如果一個事件僅用于直接封閉組件,我們也可以在 shadow host 上發(fā)送它并設置 composed: false
。這樣它就不在組件 shadow DOM 中,也不會冒泡到更高級別的 DOM。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: