本系列文章已重新編修,並在加入部分 ES6 新篇章後集結成書,有興趣的朋友可至天瓏書局選購,感謝大家支持。
購書連結 https://www.tenlong.com.tw/products/9789864344130
讓我們再次重新認識 JavaScript!
在前面兩篇文章中,我們已介紹過事件的傳遞機制,以及如何阻擋事件的冒泡與預設的行為。 那麼,作為「事件三部曲」的最後一篇,我們就來大略介紹一下,DOM 規範當中究竟提供了哪些事件。
瀏覽器可能發生的事件有很多種,了解這些事件的情境及效果,是開發 Web 應用程式不能忽略的基礎。
介面相關的事件不一定會與使用者對 DOM 的操作有關係,反而大多數與 window
物件比較相關。
load
事件:
註冊在 window
物件上,指的是網頁資源 (包括CSS、JS、圖片等) 全數載入完畢後觸發。
如果是 img
元素的 load
事件,則表示是此圖片載入完畢後觸發。
unload
、 beforeunload
事件:
與 load
事件相反,unload
與 beforeunload
事件分別會在離開頁面或重新整理時觸發,而 beforeunload
會跳出對話框詢問使用者是否要離開目前頁面。
error
事件:error
事件會在 document
或是圖片載入錯誤時觸發。<script>
標記,只有 error
事件最適合以 on-event
handler 的寫法來處理:<img src="image.jpg" onerror="this.src='default.jpg'">
像這樣,當 image.jpg
這個圖檔不存在的時候,馬上就會觸發 error
事件。此時就會透過 this.src
將 <img>
的 src 屬性替換成指定的預設圖檔,是相當實用的技巧。
若是在網頁 load 完成後才註冊 error
事件的 handler,你只會看到叉燒包或者破圖的結果,因為 error
事件不會再次被觸發,後來掛上去的 handler 就等於沒有一樣。
resize
事件:當瀏覽器 (window) 或指定元素 (element) 的「尺寸變更」時觸發。
scroll
事件:當瀏覽器 (window) 或指定元素 (element) 的「捲軸被拉動」時觸發。
DOMContentLoaded
事件:
類似於 load
事件,但不同的是,load
事件是在網頁「所有」資源都已經載入完成後才會觸發,而 DOMContentLoaded
事件是在 DOM 結構被完整的讀取跟解析後就會被觸發,不須等待外部資源讀取完成。
簡單來說, DOMContentLoaded
與 load
的時機可以用這張圖來解釋:
圖片修改自: Web Browsers : What is Web Browser
我們在 Day 12 透過 DOM API 查找節點 曾介紹過,<script>
標籤要是放在 <head> ... </head>
之間,因為還沒解析到網頁本體會有選取不到 DOM 的問題對吧?
<!DOCTYPE html>
<html>
<head>
<script>
// 因 Document 結構未載入,無效
document.getElementById('hello').textContent = 'Hello';
</script>
</head>
<body>
<div id="hello"></div>
</body>
</html>
那麼,改成這樣
<head>
<script>
document.addEventListener("DOMContentLoaded", function() {
// 當 document 結構已解析完成才會執行
document.getElementById('hello').textContent = 'Hello';
}, false);
</script>
</head>
就可以排除 <script>
標籤放在 <head> ... </head>
抓不到 DOM 的問題了。
與大家所熟知的 jQuery $( document ).ready( handler )
作用類似。
mousedown
/ mouseup
事件: 這兩個事件分別會在滑鼠點擊了某元素「按下」(mousedown) 按鈕,以及「放開」(mouseup) 按鈕時觸發。
click
事件: 當滑鼠「點擊」了某元素時觸發。
dblclick
事件: 當滑鼠「連點兩次」了某元素時觸發。
mouseenter
/ mousemove
/ mouseleave
事件:
這三個事件要放在一起看
mouseenter
事件。mousemove
事件。mouseleave
事件。這些滑鼠相關的事件,都可以透過上一篇曾介紹的 event.pageX
與 event.pageY
屬性去取得目前在網頁對應的座標。
鍵盤相關事件有下列三種,在大多數情況下會將鍵盤事件註冊在 input
的輸入框上。
keydown
事件: 「壓下」鍵盤按鍵時會觸發 keydown
事件。
keypress
事件: 除了 Shift, Fn, CapsLock 這三種按鍵外按住時會觸發,若按著不放則會連續觸發。
keyup
事件: 「放開」鍵盤按鍵時觸發。
如果我們針對同個元素同時綁定了這三個鍵盤事件,那麼這三個事件執行的順序會是:
"keydown"
"keypress"
"keyup"
若此時想要知道使用者按下的按鍵,則可以透過 event.keyCode
屬性來查詢。
註: keyCode
的對應表可以到這裡查看:https://gist.github.com/tylerbuchea/8011573 。
例如,今天你想要當使用者在 input
輸入框按下 「enter」 時,發動 submit
,就可以這樣做:
textBox.addEventListener('keydown', function(e){
// enter 的 keyCode 是 13
if( e.keyCode === 13 ){
formSubmit();
}
}, false);
像這樣,透過 e.keyCode
就可以判斷使用者目前按下的是哪個按鈕。
input
事件: 當 input
、 textarea
以及帶有 contenteditable
的元素內容被改變時,就會觸發 input
事件。
change
事件: 當 input
、select
、textarea
、radio
、checkbox
等表單元素被改變時觸發。 但與 input
事件不同的是,input
事件會在輸入框輸入內容的當下觸發,而 change
事件則是在目前焦點離開輸入框後才觸發。
submit
事件:當表單被送出時觸發,通常表單驗證都會在這一步處理,若驗證未通過則 return false;
。
focus
事件:當元素被聚焦時觸發。
blur
事件:當元素失去焦點時觸發。
Composition Event 其實指的是 compositionstart
、 compositionend
,以及 compositionupdate
這三個事件。
介紹 Composition Events 之前先來談談 DOM API 過去對輸入框偵測變化的幾個方式。
前面介紹過,常見的表單輸入框如: <input type="text">
如果要動態監聽輸入框的文字變化時, 大多會透過監聽 keydown
、keypress
、keyup
等鍵盤事件來判斷 value
是否變動,但如果是透過「複製貼上」之類的操作,就無法透過鍵盤事件來判斷。
而即使是 change
事件則是要在使用者改變內容,且焦點離開輸入框的前一刻才會被觸發。
所以後來有了 input
事件, input
事件會在輸入框的內容被改變時即時觸發,確實也解決了過去在 onChange
以及鍵盤相關事件功能不足所產生的問題。
但是,新的問題來了!
通常像這樣的搜尋框,我們會用類似 autocomplete
(自動完成) 的方式給使用者搜尋建議 (以 google 為例):
如上圖,在輸入中文的時候,通常會需要透過注音之類的輸入法來做拼字。
但是在大部分的情況下,針對「注音符號」或是「拼音文字」去給搜尋建議是沒有太大意義的。
這個時候就需要透過 Composition Events
來為輸入框做增強。
透過 Composition Events
我們可以觀察使用者在輸入框內開啟輸入法 (Input Method Editor, IME) 時,組字或選字的狀態。
Composition Events 提供三個事件給開發者監聽:分別是 compositionstart
、 compositionend
,以及 compositionupdate
。
compositionstart
: 輸入框內開啟輸入法,且正在拼字時觸發。compositionupdate
: 輸入框內開啟輸入法,且正在拼字或選字時更改了內容時觸發。compositionend
: 輸入框內開啟輸入法,拼字或選字完成,正要送出至輸入框時觸發。執行的時候像這樣:
可以看到,如果要確認使用者輸入完成並送出文字時,就可以透過 compositionend
來做最後確認。
自訂事件可以用 Event constructor
建立,同樣透過 addEventListener
去監聽,由 dispatchEvent
決定觸發的時機。
var event = new Event('build');
// 監聽事件
elem.addEventListener('build', function (e) { ... }, false);
// 觸發事件
elem.dispatchEvent(event);
若是想要在自訂事件內增加更多資料,則可以改用 CustomEvent
:
var event = new CustomEvent('build', { 'detail': elem.dataset.time });
那麼在 Event Handler 就可以透過 event
來接收:
function eventHandler(e) {
log('The time is: ' + e.detail);
}
當然,瀏覽器提供的事件相當多,今天分享的部分主要是比較常見,以及工作實務上需要特別注意的部分。
其他的多數事件你都可以在 MDN 的 Event reference 找到。