滑鼠事件是Web 開發中最常用的一類事件,畢竟滑鼠還是最主要的定位設備。DOM3 級事件中定義了9 個滑鼠事件,簡介如下。 click:在用戶單擊主滑鼠按鈕(一般是左邊的按鈕)或者按下回車鍵時觸發。這一點對確保易訪問性很重要,意味著onclick 事件處理程式既可以通過鍵盤也可以通過滑鼠執行。 db ...
滑鼠事件是Web 開發中最常用的一類事件,畢竟滑鼠還是最主要的定位設備。DOM3 級事件中定義了9 個滑鼠事件,簡介如下。
click:在用戶單擊主滑鼠按鈕(一般是左邊的按鈕)或者按下回車鍵時觸發。這一點對確保易訪問性很重要,意味著onclick 事件處理程式既可以通過鍵盤也可以通過滑鼠執行。
- dblclick:在用戶雙擊主滑鼠按鈕(一般是左邊的按鈕)時觸發。從技術上說,這個事件並不是DOM2 級事件規範中規定的,但鑒於它得到了廣泛支持,所以DOM3 級事件將其納入了標準。
- mousedown:在用戶按下了任意滑鼠按鈕時觸發。不能通過鍵盤觸發這個事件。
- mouseenter:在滑鼠游標從元素外部首次移動到元素範圍之內時觸發。這個事件不冒泡,而且在游標移動到後代元素上不會觸發。DOM2 級事件並沒有定義這個事件,但DOM3 級事件將它納入了規範。IE、Firefox 9+和Opera 支持這個事件。
- mouseleave:在位於元素上方的滑鼠游標移動到元素範圍之外時觸發。這個事件不冒泡,而且在游標移動到後代元素上不會觸發。DOM2 級事件並沒有定義這個事件,但DOM3 級事件將它納入了規範。IE、Firefox 9+和Opera 支持這個事件。
- mousemove:當滑鼠指針在元素內部移動時重覆地觸發。不能通過鍵盤觸發這個事件。
- mouseout:在滑鼠指針位於一個元素上方,然後用戶將其移入另一個元素時觸發。又移入的另一個元素可能位於前一個元素的外部,也可能是這個元素的子元素。不能通過鍵盤觸發這個事件。
- mouseover:在滑鼠指針位於一個元素外部,然後用戶將其首次移入另一個元素邊界之內時觸發。不能通過鍵盤觸發這個事件。
- mouseup:在用戶釋放滑鼠按鈕時觸發。不能通過鍵盤觸發這個事件。
頁面上的所有元素都支持滑鼠事件。除了mouseenter 和mouseleave,所有滑鼠事件都會冒泡,也可以被取消,而取消滑鼠事件將會影響瀏覽器的預設行為。取消滑鼠事件的預設行為還會影響其他事件,因為滑鼠事件與其他事件是密不可分的關係。
只有在同一個元素上相繼觸發mousedown 和mouseup 事件,才會觸發click 事件;如果mousedown 或mouseup 中的一個被取消,就不會觸發click 事件。類似地,只有觸發兩次click 事件,才會觸發一次dblclick 事件。如果有代碼阻止了連續兩次觸發click 事件(可能是直接取消click事件,也可能通過取消mousedown 或mouseup 間接實現),那麼就不會觸發dblclick 事件了。這4個事件觸發的順序始終如下:
- (1) mousedown
- (2) mouseup
- (3) click
- (4) mousedown
- (5) mouseup
- (6) click
- (7) dblclick
顯然,click 和dblclick 事件都會依賴於其他先行事件的觸發;而mousedown 和mouseup 則不受其他事件的影響。
IE8 及之前版本中的實現有一個小bug,因此在雙擊事件中,會跳過第二個mousedown 和click事件,其順序如下:
- (1) mousedown
- (2) mouseup
- (3) click
- (4) mouseup
- (5) dblclick
IE9 修複了這個bug,之後順序就正確了。使用以下代碼可以檢測瀏覽器是否支持以上DOM2 級事件(除dbclick、mouseenter 和mouseleave 之外):
var isSupported = document.implementation.hasFeature("MouseEvents", "2.0");
要檢測瀏覽器是否支持上面的所有事件,可以使用以下代碼:
var isSupported = document.implementation.hasFeature("MouseEvent", "3.0")
註意,DOM3 級事件的feature 名是"MouseEvent",而非"MouseEvents"。
滑鼠事件中還有一類滾輪事件。而說是一類事件,其實就是一個mousewheel 事件。這個事件跟蹤滑鼠滾輪,類似於Mac 的觸控板。
1. 客戶區坐標位置
滑鼠事件都是在瀏覽器視口中的特定位置上發生的。這個位置信息保存在事件對象的clientX 和clientY 屬性中。所有瀏覽器都支持這兩個屬性,它們的值表示事件發生時滑鼠指針在視口中的水平和垂直坐標。圖13-4 展示了視口中客戶區坐標位置的含義。
可以使用類似下列代碼取得滑鼠事件的客戶端坐標信息:
var div = document.getElementById("myDiv"); EventUtil.addHandler(div, "click", function(event) { event = EventUtil.getEvent(event); alert("Client coordinates: " + event.clientX + "," + event.clientY); });
運行一下
這裡為一個<div>元素指定了onclick 事件處理程式。當用戶單擊這個元素時,就會看到事件的客戶端坐標信息。註意,這些值中不包括頁面滾動的距離,因此這個位置並不表示滑鼠在頁面上的位置。
2. 頁面坐標位置
通過客戶區坐標能夠知道滑鼠是在視口中什麼位置發生的,而頁面坐標通過事件對象的pageX 和pageY 屬性,能告訴你事件是在頁面中的什麼位置發生的。換句話說,這兩個屬性表示滑鼠游標在頁面中的位置,因此坐標是從頁面本身而非視口的左邊和頂邊計算的。
以下代碼可以取得滑鼠事件在頁面中的坐標:
var div = document.getElementById("myDiv"); EventUtil.addHandler(div, "click", function(event) { event = EventUtil.getEvent(event); alert("Page coordinates: " + event.pageX + "," + event.pageY); });
運行一下
在頁面沒有滾動的情況下,pageX 和pageY 的值與clientX 和clientY 的值相等。
IE8 及更早版本不支持事件對象上的頁面坐標,不過使用客戶區坐標和滾動信息可以計算出來。這時候需要用到document.body(混雜模式)或document.documentElement(標準模式)中的scrollLeft 和scrollTop 屬性。計算過程如下所示:
var div = document.getElementById("myDiv"); EventUtil.addHandler(div, "click", function(event) { event = EventUtil.getEvent(event); var pageX = event.pageX, pageY = event.pageY; if (pageX === undefined) { pageX = event.clientX + (document.body.scrollLeft || document.documentElement.scrollLeft); } if (pageY === undefined) { pageY = event.clientY + (document.body.scrollTop || document.documentElement.scrollTop); } alert("Page coordinates: " + pageX + "," + pageY); });
3. 屏幕坐標位置
滑鼠事件發生時,不僅會有相對於瀏覽器視窗的位置,還有一個相對於整個電腦屏幕的位置。而通過screenX 和screenY 屬性就可以確定滑鼠事件發生時滑鼠指針相對於整個屏幕的坐標信息。圖13-5展示了瀏覽器中屏幕坐標的含義。
可以使用類似下麵的代碼取得滑鼠事件的屏幕坐標:
var div = document.getElementById("myDiv"); EventUtil.addHandler(div, "click", function(event) { event = EventUtil.getEvent(event); alert("Screen coordinates: " + event.screenX + "," + event.screenY); });
運行一下
與前一個例子類似,這裡也是為<div>元素指定了一個onclick 事件處理程式。當這個元素被單擊時,就會顯示出事件的屏幕坐標信息了。
4. 修改鍵
雖然滑鼠事件主要是使用滑鼠來觸發的,但在按下滑鼠時鍵盤上的某些鍵的狀態也可以影響到所要採取的操作。這些修改鍵就是Shift、Ctrl、Alt 和Meta(在Windows 鍵盤中是Windows 鍵,在蘋果機中是Cmd 鍵),它們經常被用來修改滑鼠事件的行為。DOM 為此規定了4 個屬性,表示這些修改鍵的狀態:shiftKey、ctrlKey、altKey 和metaKey。這些屬性中包含的都是布爾值,如果相應的鍵被按下了,則值為true,否則值為false。當某個滑鼠事件發生時,通過檢測這幾個屬性就可以確定用戶是否同時按下了其中的鍵。來看下麵的例子。
var div = document.getElementById("myDiv"); EventUtil.addHandler(div, "click", function(event) { event = EventUtil.getEvent(event); var keys = new Array(); if (event.shiftKey) { keys.push("shift"); } if (event.ctrlKey) { keys.push("ctrl"); } if (event.altKey) { keys.push("alt"); } if (event.metaKey) { keys.push("meta"); } alert("Keys: " + keys.join(",")); });
運行一下
在這個例子中,我們通過一個onclick 事件處理程式檢測了不同修改鍵的狀態。數組keys 中包含著被按下的修改鍵的名稱。換句話說,如果有屬性值為true,就會將對應修改鍵的名稱添加到keys數組中。在事件處理程式的最後,有一個警告框將檢測到的鍵的信息顯示給用戶。
IE9、Firefox、Safari、Chrome 和Opera 都支持這4 個鍵。IE8 及之前版本不支持metaKey 屬性。
5. 相關元素
在發生mouseover 和mouserout 事件時,還會涉及更多的元素。這兩個事件都會涉及把滑鼠指針從一個元素的邊界之內移動到另一個元素的邊界之內。對mouseover 事件而言,事件的主目標是獲得游標的元素,而相關元素就是那個失去游標的元素。類似地,對mouseout 事件而言,事件的主目標是失去游標的元素,而相關元素則是獲得游標的元素。來看下麵的例子。
<!DOCTYPE html> <html> <head> <title>Related Elements Example</title> </head> <body> <div id="myDiv" style="background-color:red;height:100px;width:100px;"></div> </body> </html>
運行一下
這個例子會在頁面上顯示一個<div>元素。如果滑鼠指針一開始位於這個<div>元素上,然後移出了這個元素,那麼就會在<div>元素上觸發mouseout 事件,相關元素就是<body>元素。與此同時,<body>元素上面會觸發mouseover 事件,而相關元素變成了<div>。
DOM通過event 對象的relatedTarget 屬性提供了相關元素的信息。這個屬性只對於mouseover和mouseout 事件才包含值;對於其他事件,這個屬性的值是null。IE8及之前版本不支持relatedTarget屬性,但提供了保存著同樣信息的不同屬性。在mouseover 事件觸發時,IE 的fromElement 屬性中保存了相關元素;在mouseout 事件觸發時,IE 的toElement 屬性中保存著相關元素。(IE9 支持所有這些屬性。)可以把下麵這個跨瀏覽器取得相關元素的方法添加到EventUtil 對象中。
var EventUtil = { //省略了其他代碼 getRelatedTarget: function(event) { if (event.relatedTarget) { return event.relatedTarget; } else if (event.toElement) { return event.toElement; } else if (event.fromElement) { return event.fromElement; } else { return null; } }, //省略了其他代碼 };
EventUtil.js
與以前添加的跨瀏覽器方法一樣,這個方法也使用了特性檢測來確定返回哪個值。可以像下麵這樣使用EventUtil.getRelatedTarget()方法:
var div = document.getElementById("myDiv"); EventUtil.addHandler(div, "mouseout", function(event) { event = EventUtil.getEvent(event); var target = EventUtil.getTarget(event); var relatedTarget = EventUtil.getRelatedTarget(event); alert("Moused out of " + target.tagName + " to " + relatedTarget.tagName); });
運行一下
這個例子為<div>元素的mouseout 事件註冊了一個事件處理程式。當事件觸發時,會有一個警告框顯示滑鼠移出和移入的元素信息。
6. 滑鼠按鈕
只有在主滑鼠按鈕被單擊(或鍵盤迴車鍵被按下)時才會觸發click 事件,因此檢測按鈕的信息並不是必要的。但對於mousedown 和mouseup 事件來說,則在其event 對象存在一個button 屬性,表示按下或釋放的按鈕。DOM的button 屬性可能有如下3 個值:0 表示主滑鼠按鈕,1 表示中間的滑鼠按鈕(滑鼠滾輪按鈕),2 表示次滑鼠按鈕。在常規的設置中,主滑鼠按鈕就是滑鼠左鍵,而次滑鼠按鈕就是滑鼠右鍵。
IE8 及之前版本也提供了button 屬性,但這個屬性的值與DOM 的button 屬性有很大差異。
- 0:表示沒有按下按鈕。
- 1:表示按下了主滑鼠按鈕。
- 2:表示按下了次滑鼠按鈕。
- 3:表示同時按下了主、次滑鼠按鈕。
- 4:表示按下了中間的滑鼠按鈕。
- 5:表示同時按下了主滑鼠按鈕和中間的滑鼠按鈕。
- 6:表示同時按下了次滑鼠按鈕和中間的滑鼠按鈕。
- 7:表示同時按下了三個滑鼠按鈕。
不難想見,DOM 模型下的button 屬性比IE 模型下的button 屬性更簡單也更為實用,因為同時按下多個滑鼠按鈕的情形十分罕見。最常見的做法就是將IE 模型規範化為DOM 方式,畢竟除IE8 及更早版本之外的其他瀏覽器都原生支持DOM 模型。而對主、中、次按鈕的映射並不困難,只要將IE 的其他選項分別轉換成如同按下這三個按鍵中的一個即可(同時將主按鈕作為優先選取的對象)。換句話說,IE 中返回的5 和7 會被轉換成DOM 模型中的0。
由於單獨使用能力檢測無法確定差異(兩種模型有同名的button 屬性),因此必須另闢蹊徑。我們知道,支持DOM 版滑鼠事件的瀏覽器可以通過hasFearture()方法來檢測,所以可以再為EventUtil 對象添加如下getButton()方法。
var EventUtil = { //省略了其他代碼 getButton: function(event) { if (document.implementation.hasFeature("MouseEvents", "2.0")) { return event.button; } else { switch (event.button) { case 0: case 1: case 3: case 5: case 7: return 0; case 2: case 6: return 2; case 4: return 1; } } }, //省略了其他代碼 };
EventUtil.js
通過檢測"MouseEvents"這個特性,就可以確定event 對象中存在的button 屬性中是否包含正確的值。如果測試失敗,說明是IE,就必須對相應的值進行規範化。以下是使用該方法的示例。
var div = document.getElementById("myDiv"); EventUtil.addHandler(div, "mousedown", function(event) { event = EventUtil.getEvent(event); alert(EventUtil.getButton(event)); });
運行一下
在這個例子中,我們為一個<div>元素添加了一個onmousedown 事件處理程式。當在這個元素上按下滑鼠按鈕時,會有警告框顯示按鈕的代碼。
在使用onmouseup 事件處理程式時,button 的值表示釋放的是哪個按鈕。此外,如果不是按下或釋放了主滑鼠按鈕,Opera 不會觸發mouseup 或mousedown事件。
7. 更多的事件信息
“DOM2 級事件”規範在event 對象中還提供了detail 屬性,用於給出有關事件的更多信息。對於滑鼠事件來說,detail 中包含了一個數值,表示在給定位置上發生了多少次單擊。在同一個元素上相繼地發生一次mousedown 和一次mouseup 事件算作一次單擊。detail 屬性從1 開始計數,每次單擊發生後都會遞增。如果滑鼠在mousedown 和mouseup 之間移動了位置,則detail 會被重置為0。
IE 也通過下列屬性為滑鼠事件提供了更多信息。
- altLeft:布爾值,表示是否按下了Alt 鍵。如果altLeft 的值為true,則altKey 的值也為true。
- ctrlLeft:布爾值,表示是否按下了Ctrl 鍵。如果ctrlLeft 的值為true,則ctrlKey 的值也為true。
- offsetX:游標相對於目標元素邊界的x 坐標。
- offsetY:游標相對於目標元素邊界的y 坐標。
- shiftLeft:布爾值,表示是否按下了Shift 鍵。如果shiftLeft 的值為true,則shiftKey的值也為true。
這些屬性的用處並不大,原因一方面是只有IE 支持它們,另一方是它們提供的信息要麼沒有什麼價值,要麼可以通過其他方式計算得來。
8. 滑鼠滾輪事件
IE 6.0 首先實現了mousewheel 事件。此後,Opera、Chrome 和Safari 也都實現了這個事件。當用戶通過滑鼠滾輪與頁面交互、在垂直方向上滾動頁面時(無論向上還是向下),就會觸發mousewheel事件。這個事件可以在任何元素上面觸發,最終會冒泡到document(IE8)或window(IE9、Opera、Chrome 及Safari)對象。與mousewheel 事件對應的event 對象除包含滑鼠事件的所有標準信息外,還包含一個特殊的wheelDelta 屬性。當用戶向前滾動滑鼠滾輪時,wheelDelta 是120 的倍數;當用戶向後滾動滑鼠滾輪時,wheelDelta 是120 的倍數。圖13-6 展示了這個屬性。
將mousewheel 事件處理程式指定給頁面中的任何元素或document 對象,即可處理滑鼠滾輪的交互操作。來看下麵的例子。
EventUtil.addHandler(document, "mousewheel", function(event){ event = EventUtil.getEvent(event); alert(event.wheelDelta); });
這個例子會在發生mousewheel 事件時顯示wheelDelta 的值。多數情況下,只要知道滑鼠滾輪滾動的方向就夠了,而這通過檢測wheelDelta 的正負號就可以確定。
有一點要註意:在Opera 9.5 之前的版本中,wheelDelta 值的正負號是顛倒的。如果你打算支持早期的Opera 版本,就需要使用瀏覽器檢測技術來確定實際的值,如下麵的例子所示。
EventUtil.addHandler(document, "mousewheel", function(event) { event = EventUtil.getEvent(event); var delta = (client.engine.opera && client.engine.opera < 9.5 ? -event.wheelDelta: event.wheelDelta); alert(delta); });
運行一下
以上代碼使用第9 章創建的client 對象檢測了瀏覽器是不是早期版本的Opera。
由於mousewheel 事件非常流行,而且所有瀏覽器都支持它,所以HTML 5 也加入了該事件。
Firefox 支持一個名為DOMMouseScroll 的類似事件,也是在滑鼠滾輪滾動時觸發。與mousewheel事件一樣,DOMMouseScroll 也被視為滑鼠事件,因而包含與滑鼠事件有關的所有屬性。而有關滑鼠滾輪的信息則保存在detail 屬性中,當向前滾動滑鼠滾輪時,這個屬性的值是-3 的倍數,當向後滾動滑鼠滾輪時,這個屬性的值是3 的倍數。圖13-7 展示了這個屬性。
可以將DOMMouseScroll 事件添加到頁面中的任何元素,而且該事件會冒泡到window 對象。因此,可以像下麵這樣針對這個事件來添加事件處理程式。
EventUtil.addHandler(window, "DOMMouseScroll", function(event) { event = EventUtil.getEvent(event); alert(event.detail); });
運行一下
這個簡單的事件處理程式會在滑鼠滾輪滾動時顯示detail 屬性的值。
若要給出跨瀏覽器環境下的解決方案,第一步就是創建一個能夠取得滑鼠滾輪增量值(delta)的方法。下麵是我們添加到EventUtil 對象中的這個方法。
var EventUtil = { //省略了其他代碼 getWheelDelta: function(event) { if (event.wheelDelta) { return (client.engine.opera && client.engine.opera < 9.5 ? -event.wheelDelta: event.wheelDelta); } else { return - event.detail * 40; } }, //省略了其他代碼 };
EventUtil.js
這裡,getWheelDelta()方法首先檢測了事件對象是否包含wheelDelta 屬性,如果是則通過瀏覽器檢測代碼確定正確的值。如果wheelDelta 不存在,則假設相應的值保存在detail 屬性中。由於Firefox 的值有所不同,因此首先要將這個值的符號反向,然後再乘以40,就可以保證與其他瀏覽器的值相同了。有了這個方法之後,就可以將相同的事件處理程式指定給mousewheel 和DOMMouse-Scroll 事件了,例如:
(function() { function handleMouseWheel(event) { event = EventUtil.getEvent(event); var delta = EventUtil.getWheelDelta(event); alert(delta); } EventUtil.addHandler(document, "mousewheel", handleMouseWheel); EventUtil.addHandler(document, "DOMMouseScroll", handleMouseWheel); })();
運行一下
我們將相關代碼放在了一個私有作用域中,從而不會讓新定義的函數干擾全局作用域。這裡定義的handleMouseWheel()函數可以用作兩個事件的處理程式(如果指定的事件不存在,則為該事件指定處理程式的代碼就會靜默地失敗)。由於使用了EventUtil.getWheelDelta()方法,我們定義的這個事件處理程式函數可以適用於任何一種情況。
9. 觸摸設備
iOS 和Android 設備的實現非常特別,因為這些設備沒有滑鼠。在面向iPhone 和iPod 中的Safari開發時,要記住以下幾點。
- 不支持dblclick 事件。雙擊瀏覽器視窗會放大畫面,而且沒有辦法改變該行為。
- 輕擊可單擊元素會觸發mousemove 事件。如果此操作會導致內容變化,將不再有其他事件發生;
如果屏幕沒有因此變化,那麼會依次發生mousedown、mouseup 和click 事件。輕擊不可單擊的元素不會觸發任何事件。可單擊的元素是指那些單擊可產生預設操作的元素(如鏈接),或者那些已經被指定了onclick 事件處理程式的元素。
- mousemove 事件也會觸發mouseover 和mouseout 事件。
- 兩個手指放在屏幕上且頁面隨手指移動而滾動時會觸發mousewheel 和scroll 事件。
10. 無障礙性問題
如果你的Web 應用程式或網站要確保殘疾人特別是那些使用屏幕閱讀器的人都能訪問,那麼在使用滑鼠事件時就要格外小心。前面提到過,可以通過鍵盤上的回車鍵來觸發click 事件,但其他滑鼠事件卻無法通過鍵盤來觸發。為此,我們不建議使用click 之外的其他滑鼠事件來展示功能或引發代碼執行。因為這樣會給盲人或視障用戶造成極大不便。以下是在使用滑鼠事件時應當註意的幾個易訪問性問題。
- 使用click 事件執行代碼。有人指出通過onmousedown 執行代碼會讓人覺得速度更快,對視力正常的人來說這是沒錯的。但是,在屏幕閱讀器中,由於無法觸發mousedown 事件,結果就會造成代碼無法執行。
- 不要使用onmouseover 向用戶顯示新的選項。原因同上,屏幕閱讀器無法觸發這個事件。如果確實非要通過這種方式來顯示新選項,可以考慮添加顯示相同信息的鍵盤快捷方式。
- 不要使用dblclick 執行重要的操作。鍵盤無法觸發這個事件。
遵照以上提示可以極大地提升殘疾人在訪問你的Web 應用程式或網站時的易訪問性。
要瞭解如何在網頁中實現無障礙訪問的內容,請訪問www.webaim.org 和http://yaccessibilityblog.com/。