阻止事件傳遞 stopPropagation

別讓上面或下面的人知道!

提醒

看不懂就代表你一定沒搞清楚事件的「傳遞流程」,罰你回去再看一次:事件傳遞機制-捕獲與冒泡

阻止向上傳遞(不要讓它往上冒泡)

以下分別示範,「阻止」與「不阻止」的差異。

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)
}

// 全部都監聽冒泡階段 click 事件
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)
}
// btn 額外設立一個阻止傳遞的事件
document.querySelector('.btn')
.addEventListener('click', function(e) {
// 阻止事件傳遞
e.stopPropagation()
}, false)

addEventBubbling('.outter')
addEventBubbling('.inner')
addEventBubbling('.btn')
1
.btn 冒泡

阻止向上傳遞(不要讓它往下捕獲)

跟剛剛一樣,差別在於一個是在「冒泡階段」阻止,一個是在「捕獲階段」阻止。

先示範一個錯誤的範例:

1
2
3
4
5
6
7
8
9
10
// outter 監聽冒泡階段
document.querySelector('.outter')
.addEventListener('click', function(e) {
// 阻止事件傳遞
e.stopPropagation()
}, false)

// 以下監聽冒泡階段
addEventBubbling('.btn')
addEventBubbling('.inner')

stop-from-bubbling

怎麼沒有用?因為你沒搞清楚順序。觸發的順序是: .btn -> .inner -> outter

所以在 .outter 阻止事件傳遞的時候 .btn.inner 早就已經被觸發完了。

正確的作法是讓 .outter 在「捕獲階段」就阻止事件傳遞:

1
2
3
4
5
6
7
8
9
10
// outter 監聽捕獲階段
document.querySelector('.outter')
.addEventListener('click', function(e) {
// 阻止事件傳遞
e.stopPropagation()
}, true)

// 以下監聽冒泡階段
addEventBubbling('.btn')
addEventBubbling('.inner')

stop-from-capturing

這時候不管怎麼點都不會觸發,因為 .outter 在「捕獲階段」就讓事件停止向下傳遞。

補充 stopImmediatePropagation

首先要知道一個元素是可以設定多個 addEventListener 的。

假設有個元素同時綁了兩個監聽器(click):

  • clickA
  • clickB

如果我想在 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')
// 在 A 阻止事件傳遞
e.stopPropagation()
}, false)
}
function clickB(selector) {
document.querySelector('.btn')
.addEventListener('click', function(e) {
console.log('clickB')
}, false)
}
// 在 .btn 監聽兩個事件(冒泡階段)
clickA('.btn')
clickB('.btn')

clickB 一樣會被觸發:

stopPropagation

改用 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')
// 在 A 立即阻止事件傳遞
e.stopImmediatePropagation()
}, false)
}
function clickB(selector) {
document.querySelector('.btn')
.addEventListener('click', function(e) {
console.log('clickB')
}, false)
}
// 在 .btn 監聽兩個事件(冒泡階段)
clickA('.btn')
clickB('.btn')

這時候 clickB 就不會被觸發了:

stopImmediatePropagation

最後做幾個補充:

  1. 注意設定監聽器的順序

在一個元素上綁定多個事件時,會按照「綁定的順序」來觸發,以前面的例子來說就是: clickA -> clickB

如果現在你把設定順序改成 clickB -> clickA,這時候 stopImmediatePropagation 就沒有作用,因為在 stopImmediatePropagation 執行前 clickB 會在那已經先被觸發了。

  1. 注意監聽的是哪個階段

其實如果你把 clickA 改成監聽「捕獲階段」,那 stopPropagation 一樣可以阻止 clickB 被觸發,我想是因為「先捕獲,在冒泡」這個傳遞機制的關係。

preventDefault 的小知識 mentor-program-day37
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×