JavaScript DOM Event (事件處理)

使用者在瀏覽網頁時會觸發很多事件 (events) 的發生,例如按下滑鼠是一種事件、按下鍵盤按鍵是一種事件、網頁或圖片完成下載時是一種事件、表單欄位值被改變是一種事件..等等。

DOM Event 定義很多種事件型態,讓你可以用 JavaScript 來監聽 (listen) 和處理 (event handling) 這些事件。

事件名稱觸發條件
blur物件失去焦點時
change物件內容改變時
click滑鼠點擊物件時
dblclick滑鼠連點二下物件時
error當圖片或文件下載產生錯誤時
focus當物件被點擊或取得焦點時
keydown按下鍵盤按鍵時
keypress按下並放開鍵盤按鍵後
keyup按下並放開鍵盤按鍵時
load網頁或圖片完成下載時
mousedown按下滑鼠按鍵時
mousemove介於over跟out間的滑鼠移動行為
mouseout滑鼠離開某物件四周時
mouseover滑鼠進入一個元素 (包含進入該元素中的子元素) 四周時
mouseup放開滑鼠按鍵時
resize當視窗或框架大小被改變時
scroll當捲軸被拉動時
select當文字被選取時
submit當按下送出按紐時
beforeunload當使用者關閉 (或離開) 網頁之前
unload當使用者關閉 (或離開) 網頁之後

接下來我們來介紹怎麼針對特定事件來綁定事件處理函式 (event handler),分別有幾種做法。

DOM Level 0 - HTML Inline Attribute

你可以在 HTML 的事件相關屬性上綁定事件處理函式,屬性的名稱是「on + 事件名稱」,屬性值則是任何的 JavaScript。

範例:

<html>
<head>
  <title>inline event handling example</title>
</head>
<body>

  <button onclick="triggerAlert();">click me</button>

  <script>
    function triggerAlert() {
      alert('Hey');
    }
  </script>
</body>
</html>

上面的例子中,當使用者點下按鈕會跳出 "Hey"。

this

你可以在 inline 的 function 傳入 this 當參數,表示 DOM 元素物件本身:

<html>
<head>
  <title>inline event handling example</title>
</head>
<body>

  <button onclick="triggerAlert(this);" data-name="Mike">click me</button>

  <script>
    function triggerAlert(ele) {
      alert('Hey ' + ele.getAttribute('data-name'));
    }
  </script>
</body>
</html>

上面的例子中,當使用者點下按鈕會跳出 "Hey Mike"。

DOM Level 0 - DOM Object Property

DOM 元素 API 也有對應的屬性,可以用來綁定事件處理函式。

上面的例子可以改寫成:

<html>
<head>
  <title>traditional event handling example</title>
</head>
<body>

  <button id="foo">click me</button>

  <script>    
    function triggerAlert() {
      alert('Hey');
    }
    
    var ele = document.getElementById('foo');
    
    // 當使用者按下 (click) 按鈕時,執行 triggerAlert 函數
    ele.onclick = triggerAlert;
  </script>
</body>
</html>

如果要取消綁定事件處理函數,將事件屬性值設為 null 就可以了:

ele.onclick = null;

DOM Level 2 - Element.addEventListener(eventType, listener)

addEventListener 方法可以用來綁定元素的事件處理函數,第一個參數 eventType 是事件名稱,第二個參數 listener 是事件處理函數。

例子:

<html>
<head>
  <title>addEventListener example</title>
</head>
<body>
  <script>
    function myAlert() {
      alert('Hey!');
    }

    // 當使用者用滑鼠點擊頁面時,執行 myAlert 函數
    document.addEventListener('click', myAlert);

    // 當網頁載入時,執行這個 callback 函數
    window.addEventListener('load', function() {
      alert('頁面已載入!');
    });
  </script>
</body>
</html>

addEventListener 相較於 DOM Level 0 的方法是它可以對一個元素同時指定多個事件處理函數:

<html>
<head>
  <title>addEventListener example</title>
</head>
<body>
  <script>
    function myAlert1() {
      alert('Hey!');
    }

    function myAlert2() {
      alert('Hello!');
    }
    
    // 當使用者用滑鼠點擊頁面時,執行 myAlert1 函數
    document.addEventListener('click', myAlert1);

    // 當使用者用滑鼠點擊頁面時,執行 myAlert2 函數
    document.addEventListener('click', myAlert2);
  </script>
</body>
</html>

在上面的例子中點擊頁面依序會跳出 "Hey!" "Hello!"。

但要特別注意的是依不同瀏覽器的實作,執行順序可能不會等於事件處理函數指定的順序。

IE 在 IE9 開始才有支援 addEventListener。

DOM Level 2 - Element.removeEventListener(eventType, listener)

removeEventListener 原來取消透過 addEventListener 綁定的事件處理函數,第一個參數 eventType 是事件名稱,第二個參數 listener 是先前綁定的事件處理函數。

用法:

<html>
<head>
  <title> removeEventListener example</title>
</head>
<body>
  <button id="foo">click me</button>
  
  <script>
    function myAlert() {
      alert('Hey!');
    }
    
    var ele = document.getElementById('foo');

    // 綁定 click 事件處理函數
    ele.addEventListener('click', myAlert);

    // 移除剛綁定的 click 事件處理函數
    ele.removeEventListener('click', myAlert);
  </script>
</body>
</html>
IE 在 IE9 開始才有支援 removeEventListener。

Legacy IE - attachEvent(eventType, listener), detachEvent(eventType, listener)

在 IE9 以下可以用 attachEvent 和 detachEvent 這兩個 IE 專有 (proprietary) 的方法來取代 addEventListener 和 removeEventListener。

注意用這兩個方法時的事件名稱前面要加上 "on",用法:

<html>
<head>
  <title>IE example</title>
</head>
<body>
  <button id="foo">click me</button>
  
  <script>
    function myAlert() {
      alert('Hey!');
    }
    
    var ele = document.getElementById('foo');

    // 綁定 click 事件處理函數
    ele.attachEvent('click', myAlert);

    // 移除剛綁定的 click 事件處理函數
    ele.detachEvent('click', myAlert);
  </script>
</body>
</html>

Event Bubbling (事件氣泡) VS Event Capturing (事件捕捉)

DOM 中的事件有傳播 (event flow) 的概念,當 DOM 事件發生時,事件會先由外到內 (capturing phase)、再由內到外 (bubbling phase) 的順序來傳播。

什麼意思?例如我們看這一個 HTML DOM 結構:

<html>
<head>
    <title>event flow example</title>
</head>
<body>
    <div>
        <ul>
            <li></li>
        </ul>
    </div>
</body>
</html>

當使用者點擊 li 元素時,事件觸發的順序是:

  1. Capturing 捕捉階段:document -> <html> -> <body> -> <div> -> <ul> -> <li>
  2. Bubbling 氣泡階段:<li> -> <ul> -> <div> -> <body> -> <html> -> document

DOM 中的元素會按照上面的順序依序地觸發其 click 事件。

在 addEventListener 和 removeEventListener 方法中,有第三個參數布林值 useCapture 用來指定事件處理函數是要在 Capturing 階段或 Bubbling 階段被執行,false (預設) 表示 Bubbling,true 表示 Capturing。

Event Object

當事件處理函數被執行時,會傳入一個參數代表 event object。

例如:

element.onclick = function(event) {
    // ...
};

// 或是

element.addEventListener('click', function(event) {
    // ...
});

在 IE9 以下的 DOM Level 0 或 attachEvent 方法不會傳入 event object,但有一個 global 的 window.event object 可以用來代替,所以跨瀏覽器 (cross-browser) 的寫法可以改成:

element.onclick = function(event) {
    event = event || window.event;
    // ...
};

Event Object 有幾個常用的屬性 (property):

屬性說明
type返回事件類型,例如 "click"
target指向觸發事件的 DOM element
currentTarget在 event bubbling 階段中,指向目前執行的事件處理函數是綁定在哪個 DOM element 上
timeStamp事件發生時的時間 timestamp (單位是 milliseconds 毫秒)
eventPhase返回為一個數字,表示事件處於目前所處的傳播狀態 (event flow),有這些值:
0: None
1: capturing phase
2: target phase
3: bubbling phase

當 MouseEvent (滑鼠事件) 發生時,Event Object 有幾個常用的屬性:

屬性說明
which當按下滑鼠按鍵,取得是哪個按鍵,可能的值有:
0: 非按鍵
1: 左鍵
2: 中鍵或滾輪
3: 右鍵
relatedTarget指向參與事件的相關 DOM element。用在 mouseover 事件,表示剛離開的那個 DOM element;用在 mouseout 事件,表示剛進入的那個 DOM element
pageX當按下滑鼠時 (或觸控螢幕時),取得距離頁面 (document) 最左上角的水平距離 (單位 pixel)
pageY當按下滑鼠時 (或觸控螢幕時),取得距離頁面 (document) 最左上角的垂直距離 (單位 pixel)
MouseEvent 像是 mousedown, mouseup 和 mouseover 事件。

當 KeyboardEvent (鍵盤事件) 發生時,Event Object 有幾個常用的屬性:

屬性說明
keyCode當 keypress 事件時,返回 character code;當 keydown 或 keyup 事件時,返回 key code
which值同 keyCode
charCode當 keypress 事件時,返回 character code
altKey布林值 (boolean),用來判斷使用者是否有按 alt 鍵
ctrlKey布林值 (boolean),用來判斷使用者是否有按 ctrl 鍵
metaKey布林值 (boolean),用來判斷使用者是否有按 meta 鍵
shiftKey布林值 (boolean),用來判斷使用者是否有按 shift 鍵
KeyboardEvent 像是 keypress, keydown 和 keyup 事件。
Character codes 是一個代表 ASCII character 的數字;Key codes 則是一個數字,代表鍵盤上的某個按鍵。

event.stopPropagation()

了解事件傳播的概念後,你可能會想到,那我怎麼不要讓事件傳播下去?答案就是使用 event object 的 stopPropagation 方法。

例子:

element.addEventListener('click', function(event) {
    event.stopPropagation();
    // ...
});
IE 在 IE9 開始才有支援 stopPropagation。
在 IE9 以下你可以使用 IE 專有的 cancelBubble 屬性 event.cancelBubble = true;

event.preventDefault()

event object 有一個 preventDefault 方法用來取消瀏覽器預設的行為。

瀏覽器預設行為舉例來說像是:

  • 點擊一個超連結後,會載入新的頁面
  • 在表單輸入欄位中輸入 enter 會送出表單

例如:

element.addEventListener('click', function(event) {
    event.preventDefault();
    // ...
});
IE 在 IE9 開始才有支援 preventDefault。
在 IE9 以下你可以使用 IE 專有的 returnValue 屬性 event.returnValue = false;

常見的事件處理範例 (Examples)

onclick event

// 當使用者用滑鼠點擊螢幕 (或用手點擊觸控螢幕) 時
document.body.addEventListener('click', function(event) {

    // 把字體改成黃色
    event.target.style.color = 'yellow';
});

onkeydown event

// 當使用者在網頁中按下鍵盤按鍵時
document.onkeydown = function(event) {

    if (event.keyCode === 89 && event.ctrlKey) {
        alert('你同時按下 "control + y"'); 
  
    } else if (event.which === 90 && event.ctrlKey ){
        alert('你同時按下 "control + z"'); 
    }
};

onbeforeunload event

// 當使用者要離開或關閉目前頁面時
window.onbeforeunload = function(event) {

    // 返回要顯示給使用者看的提醒文字
    return '你確定要離開本頁面嗎?';
};
因為在 DOM 事件中,各家瀏覽器的實作會略有差異 (像是 IE9 以下),推薦可以使用 jQuery 來更方便的處理 DOM 事件。