別讓上面或下面的人知道!
提醒
看不懂就代表你一定沒搞清楚事件的「傳遞流程」,罰你回去再看一次:事件傳遞機制-捕獲與冒泡
阻止向上傳遞(不要讓它往上冒泡)
以下分別示範,「阻止」與「不阻止」的差異。
HTML:
1 2 3 4 5
| <div class="outter"> <div class="inner"> <button class="btn">btn</button> </div> </div>
|
先示範第一種,在沒有阻止事件傳遞時應該是這樣子:
1 2 3 4 5 6 7 8 9 10 11 12
| function addEventBubbling(selector) { document.querySelector(selector) .addEventListener('click', function(e) { console.log(selector, '冒泡') }, false) }
addEventBubbling('.outter') addEventBubbling('.inner') addEventBubbling('.btn')
|
Output:
1 2 3
| .btn 冒泡 .inner 冒泡 .outter 冒泡
|
現在讓 .btn
再加上一個事件來「阻止事件傳遞」,結果就不一樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function addEventBubbling(selector) { document.querySelector(selector) .addEventListener('click', function(e) { console.log(selector, '冒泡') }, false) }
document.querySelector('.btn') .addEventListener('click', function(e) { e.stopPropagation() }, false)
addEventBubbling('.outter') addEventBubbling('.inner') addEventBubbling('.btn')
|
阻止向上傳遞(不要讓它往下捕獲)
跟剛剛一樣,差別在於一個是在「冒泡階段」阻止,一個是在「捕獲階段」阻止。
先示範一個錯誤的範例:
1 2 3 4 5 6 7 8 9 10
| document.querySelector('.outter') .addEventListener('click', function(e) { e.stopPropagation() }, false)
addEventBubbling('.btn') addEventBubbling('.inner')
|
怎麼沒有用?因為你沒搞清楚順序。觸發的順序是: .btn
-> .inner
-> outter
所以在 .outter
阻止事件傳遞的時候 .btn
跟 .inner
早就已經被觸發完了。
正確的作法是讓 .outter
在「捕獲階段」就阻止事件傳遞:
1 2 3 4 5 6 7 8 9 10
| document.querySelector('.outter') .addEventListener('click', function(e) { e.stopPropagation() }, true)
addEventBubbling('.btn') addEventBubbling('.inner')
|
這時候不管怎麼點都不會觸發,因為 .outter
在「捕獲階段」就讓事件停止向下傳遞。
首先要知道一個元素是可以設定多個 addEventListener
的。
假設有個元素同時綁了兩個監聽器(click):
如果我想在 clickA
觸發時阻止事件傳遞到 clickB
,這時候沒辦法用 stopPropagation
來阻止,因為這兩個事件都綁在同個元素(層級)上。
這時候得用 stopImmediatePropagation
,在 clickA
觸發的時候執行,這樣 clickB
就不會被觸發了。
先示範 stopPropagation
的情況:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function clickA(selector) { document.querySelector('.btn') .addEventListener('click', function(e) { console.log('clickA') e.stopPropagation() }, false) } function clickB(selector) { document.querySelector('.btn') .addEventListener('click', function(e) { console.log('clickB') }, false) }
clickA('.btn') clickB('.btn')
|
clickB
一樣會被觸發:
改用 stopImmediatePropagation
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function clickA(selector) { document.querySelector('.btn') .addEventListener('click', function(e) { console.log('clickA') e.stopImmediatePropagation() }, false) } function clickB(selector) { document.querySelector('.btn') .addEventListener('click', function(e) { console.log('clickB') }, false) }
clickA('.btn') clickB('.btn')
|
這時候 clickB
就不會被觸發了:
最後做幾個補充:
- 注意設定監聽器的順序
在一個元素上綁定多個事件時,會按照「綁定的順序」來觸發,以前面的例子來說就是: clickA
-> clickB
如果現在你把設定順序改成 clickB
-> clickA
,這時候 stopImmediatePropagation
就沒有作用,因為在 stopImmediatePropagation
執行前 clickB
會在那已經先被觸發了。
- 注意監聽的是哪個階段
其實如果你把 clickA
改成監聽「捕獲階段」,那 stopPropagation
一樣可以阻止 clickB
被觸發,我想是因為「先捕獲,在冒泡」這個傳遞機制的關係。