JavaScript Capture Bubble DOM事件獲取&冒泡

JavaScript Capture Bubble DOM事件獲取&冒泡

March 21, 2019

·

5 min read

JavaScript 處理 DOM 事件上的獲取和冒泡,實務上滿常用到的觀念,可以透過這方式解決一些麻煩問題,例如: popup 視窗的關閉、內外層 DOM 互動關係,另外事件獲取、冒泡也幾乎是面試必考題。

DOM 一般事件綁定

下面的例子,我 container 先綁定 click 事件,再綁定 first,各自彈出自己的 id 名稱,那哪個會先 alert 出來。

<div id="container">container
  <ul id="list">ist
    <li id="first">number 1</li>
  </ul>
</div>
...
<script>
document.getElementById('container').addEventListener('click',function(e){
  alert(this.id);
});
document.getElementById('first').addEventListener('click',function(e){
  alert(this.id);
});
</script>

會是 first 先彈跳出來,因為綁定事件順序並不是代表執行順序,單純只是哪個 DOM 先綁定事件監聽,實際執行序還是要依照 DOM 父子關係判定,除非是綁定到同個 DOM 上,才會依照先後綁定順序執行。

事件傳遞 Capture Bubble

我們可以在 addEventListener(‘click’,function(){}, true),來決定 useCapture 參數的 boolean,預設沒帶會是設為 false,當 usecaptue 為 true 時,事件觸發會先經由 DOM tree 一路往子層到目標為止,之後再冒泡上去父層,這樣一個完整的流程就是事件獲取與冒泡。

最重要就是我們有辦法阻止事件獲取冒泡的傳遞,利用 event.stopPropagation function,就可以阻止事件往後傳遞。

另外 event 還有提供物件 eventPhase,會回傳 0~4 的數值,讓我們可以清楚知道這個事件到什麼階段。

eventPhase: 0 沒有事件
eventPhase: 1 獲取階段,會以物件父層一直到最高開始執行,最頂端會是Window,
              再來Document,Html,一直到目標為止。
eventPhase: 2 目標階段,這代表事件執行到目標
eventPhase: 3 冒泡階段,會由目標物件的第一層父層開始,一路往上到最頂端window為止。

EventListener usecapture MDN

JavaScript dompass
JavaScript dompass

// get All elements
const nodeList = [...document.querySelectorAll("*")];

// add All elements eventListener
nodeList.forEach((elem) => {
  // use true is for capture
  elem.addEventListener(
    "click",
    (e) => alert(`capture ${elem.tagName} phase:${e.eventPhase}`),
    true
  );
  // default false is for bubble
  elem.addEventListener("click", (e) =>
    alert(`bubble: ${elem.tagName} phase:${e.eventPhase}`)
  );
});

簡易獲取&冒泡

MDN eventphase flow (MDN 教學)

Capture Bubble 實用範例

這邊我們用最經典的 popup 例子,需求是要點擊按鈕 click 會讓 popup 顯示的,但在這邊我們希望 popup 內部點擊不會關閉 popup,但是點外部隨便空間會關閉 popup。

首先監聽 body 點擊會關閉 popup,再來監聽 openPop 按鈕點擊讓 popup 打開,這邊還多下了 e.stopPropagation();,防止我點擊 popup 打開觸發 body 點擊被關閉,因為這樣可以中斷點擊 popup 往上冒泡觸發 body 事件。

再來是 popup 本身需要能點選內部內容,同樣我們也對 popup 使用 e.stopPropagation(); ,讓我們可以點擊 popup 裡面的內容、按鈕。

<button id="openPop">open popup</button>
<div id="text"></div> // fake wording
<div id="popup" class="">
  <span id="closePop">x</span>
  <div>
    This is popup
    <button onclick="alert('hello')">Alert Button</button>
   </div>
</div>
...
const popup = document.getElementById('popup');
document.getElementById('openPop').addEventListener('click',(e)=>{
    e.stopPropagation();
    popup.classList.add("active");
});

document.body.addEventListener('click',()=>{
    close();
});

popup.addEventListener('click',(e)=>{
    e.stopPropagation();
});

document.getElementById('closePop').addEventListener('click',()=>{
    close();
});

function close () {
    popup.classList.remove('active');
}

心得

這觀念真心覺得實用,很多疑難雜症可以處理,或是可以少綁一些 eventlistener,利用父層子層獲取冒泡關係,搭配 stopPropagation。