邊做邊學 jQuery 系列 7-jQuery 事件處理
【事件處理】
網頁如果對使用者點選拖移動作都沒有任何反應,與報紙或書本一樣純供閱讀,瀏覽使用的樂趣肯定要大打折扣。要把握與使用者互動的時機,就非得透過事件機制不可。雖然我們到目前才開始談事件,但先前的範例中,.click(function() { ... }) 的寫法早已不可避免地多次扮演過關鍵角色。
大部分開發人員初學 Javascript 的事件寫法,大多長得像這個樣子:
<span id="mySpan" onclick="spnClick();">Click</span>
<script type="text/javascript">
function spnClick() {
alert("Clicked!");
}
</script>
或者我們也可以寫成 document.getElementById("mySpan").onclick = spnClick; 不過這種寫法很粗魯地將整個事件佔為己有,不管是否原先上面是已經掛載了其他事件函數,並不嚴謹。IE 提供了 attachEvent()、Firefox 提供了 attchEventListener() 可以在一個元素事件上重覆掛載多個事件函數。看到不同瀏覽器要用不同寫法先別煩惱,jQuery 當然已經替我們解決了跨瀏覽器問題,在 jQuery 中,我們要掛上事件函數,可以透過統一的寫法:
$(...).bind(type, data, function() { ... });
依元素的種類,type 用來指名要掛載的事件名稱字串 (可以是 blur、focus, load、resize、scroll、unload、click、dblclick、mousedown、mouseup、mousemove、mouseover、mouseout、mouseenter、mouseleave、change、select、submit、keydown、keypress、keyup、error 其中之一,多個事件名稱以空白相接則可將同一函數設在多個事件上)
事件說明blur元素失去焦點時focus元素取得焦點時load元素完成載入時resize元素大小改變時scroll 當捲軸捲動時unload使用者關閉離開網頁時click滑鼠點擊元素時dblclick滑鼠連點二下元素時mousedown按下滑鼠按鍵時mouseup放開滑鼠按鍵時mousemove介於 mouseover 跟 mouseout 間的滑鼠移動mouseover滑鼠進入元素範圍時(滑鼠離開子元素但仍在其元素範圍內時也會觸發 mouseover)mouseout滑鼠離開元素範圍時(在元素範圍內,滑鼠進入子元素範圍時也會觸發 mouseout)mouseenter滑鼠進入元素範圍時mouseleave滑鼠離開元素範圍時change欄位內容改變時select使用者選取元素中的文字時submitHTML Form 送出時keydown按下鍵盤按鍵時keypress按下按鍵後放開按鍵前觸發keyup放開鍵盤按鍵時
回到 $(...).bind(type, data, function() { ... }); 上,data 函數主要是用來傳遞額外的參數,這在多個事件共用函數時特別有用,例如:
function sharedEvent(e) {
alert("Data=" + e.data);
}
$("span").bind("click", "SpanClick", sharedEvent);
$("div").bind("click", "DivClick", sharedEvent);
不過,一向以簡潔見長的 jQuery,在事件宣告上有更簡便的寫法,例如 $(...).bind("click", function() { ... }) 可以直接寫成 $(...).click(function() { ... });
要卸除事件函數則可透過 unbind([type], [fn]),例如: unbind() 可卸除所有事件函數、unbind("click") 可以卸除所有的click事件函數、unbind("click", funcA) 則可只卸除 funcA。理論上,元素由 DOM 移除時應該要將其上所掛載的事件函數全部卸除以避免惱人的記憶體洩漏 (Memory Leak) 問題,所幸 jQuery 在這方面下了不少功夫,幾乎都能自動在 unload 時加上處理,除非有特殊的目的要卸除事件才要特別 unbind(),倒不需額外傷腦筋。
事件允許多重宣告,並會依宣告先後依序觸發,如果我們要由程式觸發事件,做法也很簡單,$(...).trigger(event, data); 即可(event 可以是事件名稱字串、jQuery.Event 物件、或是自訂物件,詳細說明可見 API 參考文件),或是比照宣告時,簡寫成 $(...).click();,差別只在於中間不傳入事件函數,例如:
$("input:button").click(function() { alert("A"); });
$("input:button").click(function() { alert("B"); });
$("input:button").click();
【存取事件資訊】
在事件函數中,可以透過 this 存取觸發事件的元素物件,如果要取得滑鼠座標、按鍵內容等資訊,則在函數中接收 event 參數取得事件資訊物件。例如: mousedown 事件時,可以透過 pageX / pageY 取得相對於網頁的滑鼠座標,screenX / screenY 取得相對於螢幕的滑鼠座標、keydown 事件時,則可由 which 取得按鍵碼。以下為範例:
$("#Inner").mousedown(function(evt) {
alert("P:" + evt.pageX + "," + evt.pageY);
});
$("input").keydown(function(evt) {
alert(evt.which);
});
除了元素的既有事件外,我們也可以自訂事件,方法是先 bind 到自訂的事件名稱上,之後再以 trigger 觸發,例如:
$("div").bind("beat", function(event, attacker, weapon) {
alert("被" + attacker + "用" + weapon + "痛扁一頓!"); });
//不傳入 event
$("div").trigger("beat", [ "Jeffrey", "狼牙棒" ]);
//傳入 event
var e = jQuery.Event("beat"); //jQuery 1.3+ 支援
$("div").trigger(e, [ "Darkthread", "愛的小手" ]);
【事件物件】
關於事件物件 (jQuery.Event),在此針對幾個常用屬性、方法補充說明: (事件物件自 jQuery 1.3 起做了一番改寫,在此以 jQuery 1.3 版本為準)
1. 事件函數中除了用 this 可以存取被觸發的元素外,還有幾個相關屬性: target 適用於由 Bubble 引發事件時,指向真正觸發事件的元素,用一個例子較易說明: 有一個 <div id="d"><span id="s">TEXT</span></div>,當 span 被 click 時,會進一步也觸發 div 的 click 事件,因此若寫成
$("div").click(function(e) {
alert($(this).attr("id"));
alert($(e.target).attr("id"));
});
就會分別得到 "d" 與 "s"。屬性 currentTarget 原則上就等同於 this。relatedTarget 用於父子元素間 mouseover 與 mouseout 事件切換時偵察之用,稍候會再另做討論。
2. IE 原本不支援 pageX / pageY,jQuery 補上了。
3. result 可用於同一事件的多個事件函數間傳遞資料,例如:
$("div").click(function() { return "First Event Handler's Result"; });
$("div").click(function(e) {
alert(e.result);
}).click();
4. 某些事件函數中,可以控制阻止作業繼續進行,例如:<a> 的 onclick 事件可以阻止連向該連結、<form> 的 onsubmit 可以拒絕將表單送出。在做法上各家瀏覽器有所不同,jQuery 則統一可透過 event.preventDefault() 達成取消的目的。例如: $("a").click(function(e) { e.preventDefault(); }); 將會阻止瀏覽器轉接超連結。
5. 借用第一點中提到的 Bubble 原理,span 被點選時,除了span 的 click 事件被觸發,span 的父元素 div 也會被觸發 click 事件,就像氣泡一樣一路浮上去。如果我們希望 span click 事件觸發後就結束,不要再觸發其父元素的 click 事件,可以使用 event.stopPropagation() 達到目標。如下例,將 e.stopPropagation(); 前方的註解移除,就只會看到 alert SPAN:
$("div").click(function(e) { alert("DIV"); });
$("span").click(function(e) {
alert("SPAN");
//e.stopPropagation();
});
6. event.stopImmediatePropagation() 類似 event.stopPropagation() 的概念,但停止觸發的對象除了父元素外,還包含同一元素針對該事件多重宣告的其他函數
【好用的 mouseenter、mouseleave】
先前在談 relatedTarget 時提到它跟 mouseover / mouseout 有關,如果不了解 mouseover、mouseout 有什麼缺點,就不會知道 mouseenter / mouseleave 好在哪裡。
基本的概念是,當滑鼠滑入元素範圍時,會先觸發 mouseover、接著在元素上游移時會觸發 mousemove、等移出元素時會觸發 mouseout,邏輯看來很清楚,應該沒有什麼問題。當元素中包有子元素時,這一切就變了調,滑鼠在父元素中移動時,當其進入子元素的範圍,會觸發父元素的 mouseout,才觸發子元素的 mouseover。換句話說,雖然子元素也被包含在父元素中,但進到子元素領空時,瀏覽器的認知是這也算離開父元素的範圍(雖然我們較傾向認定子元素也是父元素的一部分),這在處理邏輯上會造成一些困擾。光用描述的有點抽象,因此我們用一個實例來展示它有多機車:
<html>
<head>
<style type='text/css'>
div { border: solid 1px black; }
#Outer { width: 100px; height: 100px; }
#Inner { width: 50px; height: 50px; margin: 25px; }
</style>
<script type='text/javascript' src='http://ajax.googleapis.com/ajax/libs/jquery/1.3.1/jquery.js'></script>
<script type='text/javascript'>
$(function() {
$("div:not(#dvDisp)").bind("mouseover", "Over", mouseEvent)
.bind("mouseout", "Out", mouseEvent);
function mouseEvent(evt) {
$("#dvDisp").append("<span>" + $(this).attr("title") + ":" + evt.data + "-></span>");
}
});
</script>
</head><body>
<div id="Outer" title="外">
<div id="Inner" title="內"></div>
</div><hr />
<div id="dvDisp"></div>
</body>
</html>
當我們用滑鼠從左到右一口氣穿透 Outer 與 Inner 兩個 div 時(如下圖中的箭頭方向),觸發事件的順序是: 外:Over -> 外:Out -> 內:Over -> 外:Over -> 內:Out -> 外:Out -> 外:Over -> 外:Out,夠複雜吧?
大部分時候,我們期望看到的應是"外:Over-> 內:Over -> 內:Out -> 外:Out" 才對。IE 很貼心地多提供了 mouseenter、mouseleave 實踐我們理想中的事件順序,但非 IE 瀏覽器怎麼辦? 這回 jQuery 又施展魔法,讓 mouseenter / mouseleave 變成跨瀏覽器的事件。程式小改:
$("div:not(#dvDisp)").bind("mouseenter", "Enter", mouseEvent)
.bind("mouseleave", "Leave", mouseEvent);
function mouseEvent(evt) {
$("#dvDisp").append("<span>" + $(this).attr("title") + ":" + evt.data + "-></span>");
}
結果變成令人感動的"外:Enter -> 內:Enter -> 內:Leave- > 外:Leave",而且在 FireFox 中也可以通。
再一次,jQuery 讓跨瀏覽器的艱鉅工程變簡單了。
【強化型事件裝卸功能】
除了標準的 bind 之外,有時我們會需要一次性的事件處理邏輯,傳統做法是在事件函數中將自己 unbind。jQuery 則有另一個 one() 可以省去我們自己 unbind 的功夫,在事件觸發後就會自動卸除。
另外,針對 mouseover、mouseout 時要分別呼叫對應的事件函數,jQuery 提供了 hover(overFunc、outFunc) 的一次宣告法,可以再省點工。而如果是第一次點選執行 fn1,第二次點選 fn2,第三次 fn3 的情境,jQuery 則有 toggle(fn1、fn2、fn3、...) 的簡便宣告方式。
另外,使用 $("...").bind(...) 的寫法,只會作用在 bind(...) 執行當下已存在的元素上,若事後又有符合該 Selector 的元素新增,並不會自動掛上該事件,需要額外的程式邏輯再處理一次。
jQuery 1.3 起有個 live()與die(),跟 bind() / unbind() 的目標相同,但是可以做到未來符合 Selector 的元素一旦出現,就會自動掛上事件的功能。請看以下示範:
<html>
<head>
<style type='text/css'>
div { background-color: #dddddd; }
</style>
<script type='text/javascript' src='http://ajax.googleapis.com/ajax/libs/jquery/1.3.1/jquery.js'></script>
<script type='text/javascript'>
$(function() {
$("#xxx").bind("click", function() { alert("YA!"); });
$("input:button").click(function() {
$("body").append("<div id=\"xxx\">XXX</span>");
});
});
</script>
</head><body>
<input type="button" value="TEST" />
</body>
</html>
這個範例是不會成功的,因為在第 9 列 $("#xxx").bind(...) 執行時,<div id="xxx"> 還不存在,等按鈕新增後,並未再 bind 一次,因此上面沒有任何事件。但如果我們把第 9 列的 bind 改為 live (記得要用 jQuery 1.3+),就可以看到神奇的事發生,先對不存在的元素宣告事件,待元素一被建立,事件立即生效。
【範例檔案下載】
我們在乎每一位開發人員的感受 : 到 (Twitter) 公開您的想法、到 (論壇) 公開您的想法、寫悄悄話告訴我們
回到首頁