鼠標與滾輪事件
鼠標事件
“DOM3級事件”中定義了9個鼠標事件。
click:在單擊主鼠標按鈕(一般是左鍵)或者按下回車時觸發;這意味着onclick事件處理程序既可以通過鼠標也可以通過鍵盤執行。
dbclick:雙擊主鼠標按鈕(一般是左鍵)或者按下回車鍵時觸發。
mousedown:按下任意鼠標按鈕時觸發;不能通過鍵盤觸發。
mouseup:釋放鼠標按鈕時觸發;不能通過鍵盤觸發。
mouseenter:在鼠標光標從元素外部首次移動到元素范圍之內時觸發;這個事件不冒泡,而且光標移動到元素的后代元素上不會觸發;IE、Firefox9+和Opera支持這個事件。
mouseleave:在鼠標光標從元素上方移動到元素范圍之外時觸發;這個事件不冒泡,而且光標移動到元素的后代元素上不會觸發;IE、Firefox9+和Opera支持這個事件。
mousemove:在鼠標光標在元素內部移動時重復地觸發;不能通過鍵盤觸發。
mouseout:在鼠標位於一個元素上方,然后移動到另一個元素時觸發,另一個元素可以是這個元素的子元素;不能通過鍵盤觸發。
mouseover:在鼠標位於一個元素外部,然后將其首次移動到另一個元素邊界之內時觸發;不能通過鍵盤觸發。
只有在同一個元素上相繼觸發mousedown和mouseup,才會觸發click事件;類似地,只有在同一個元素上觸發兩次click事件,才會觸發一次dbclick事件。
這四個事件的觸發順序如下:
(1)mousedown
(2)mouseup
(3)click
(4)mousedown
(5)mouseup
(6)click
(7)dbclick
除了mouseenter、mouseleave、dbclick是“DOM3級事件”新增之外,其它事件都是“DOM2級事件”中定義的。
可以使用如下代碼檢測瀏覽器是否支持“DOM2級”鼠標事件:
1 var isSupported = document.implementation.hasFeature("MouseEvents", "2.0"); 2 alert(isSupported); //true
或者是否支持“DOM3級”鼠標事件:
1 var isSupported = document.implementation.hasFeature("MouseEvent", "3.0"); 2 alert(isSupported); //true
注意:“DOM3級”鼠標事件的feature名為MouseEvent;而“DOM2級”為MouseEvents。
客戶區坐標位置
通過事件對象event的clientX和clientY屬性,可以訪問事件發生時鼠標指針在視窗中的水平和垂直坐標。
1 event_util.addHandler(document, "click", function(event) { 2 event = event_util.getEvent(event); 3 alert("鼠標指針客戶區坐標為:水平距離—" + event.clientX + ";" + "垂直距離—" + event.clientY + ""); 4 });
頁面坐標位置
通過事件對象event的pageX和pageY屬性,可以訪問事件發生時鼠標指針在頁面中的水平和垂直坐標。
1 event_util.addHandler(document, "click", function(event) { 2 event = event_util.getEvent(event); 3 alert("鼠標指針頁面坐標為:水平距離—" + event.pageX + ";" + "垂直距離—" + event.pageY); 4 });
在頁面沒有滾動的情況下,pageX和pageY的值與clientX和clientY的值相等。
IE中的頁面坐標位置
IE8及更早版本不支持事件對象上的頁面坐標,不過可以使用客戶區坐標和滾動信息計算出來。
需要用到document.body(混雜模式)或者document.documentElement(標准模式)中的scrollLeft和scrollTop屬性。
計算過程如下:
1 event_util.addHandler(document, "click", function(event) { 2 event = event_util.getEvent(event); 3 var pageX = event.pageX; 4 var pageY = event.pageY; 5 if(pageX === undefined) { 6 pageX = event.clientX + (document.body.scrollLeft || document.documentElememt.scrollLeft); 7 }; 8 if(pageY === undefined) { 9 pageY = event.clientY + (document.body.scrollTop || document.documentElememt.scrollTop); 10 }; 11 alert("鼠標指針頁面坐標為:水平距離—" + pageX + ";" + "垂直距離—" + pageY); 12 });
屏幕坐標位置
通過事件對象event的screenX和screenY屬性,可以訪問事件發生時鼠標指針在屏幕中的水平和垂直坐標。
1 event_util.addHandler(document, "click", function(event) { 2 event = event_util.getEvent(event); 3 alert("鼠標指針屏幕坐標為:水平距離—" + event.screenX + ";" + "垂直距離—" + event.screenY); 4 });
修改鍵
Shift、Ctrl、Alt、Meta(或Windows或Cmd),它們經常被用來修改鼠標事件的行為。
DOM為此規定了4個屬性:shiftKey、ctrlKey、altKey、metaKey,表示這些修改鍵的狀態。
這些屬性中包含的都是布爾值,如果值為true,表示相應的鍵被按下。
當某個鼠標事件發生時,通過檢測這4個屬性,就能確定用戶是否按下某個修改鍵。
1 event_util.addHandler(document, "click", function(event) { 2 event = event_util.getEvent(event); 3 var keys = new Array(); 4 if(event.shiftKey) { 5 keys.push("shift"); 6 }; 7 if(event.ctrlKey) { 8 keys.push("ctrl"); 9 }; 10 if(event.altKey) { 11 keys.push("alt"); 12 }; 13 if(event.metaKey) { 14 keys.push("meta"); 15 }; 16 alert("點擊鼠標的同時按下了:" + keys.join(",")); 17 });
點擊鼠標的同時按下Windows鍵,檢測不出來,為何?
IE9、Firefox、Safari、Chrome和Opera都支持這4個屬性;IE8及更早版本不支持metaKey屬性。
相關元素
對mouseover事件而言,事件的主目標就是獲得光標的元素,而相關元素就是失去光標的那個元素;
對mouseout事件而言,事件的主目標就是失去光標的元素,而相關元素就是獲得光標的那個元素。
DOM通過event事件對象的relatedTarget屬性提供了相關元素的信息。
這個屬性只對mouseover和mouseout事件才包含值;對於其他事件,其值為null。
IE8及之前版本不支持relatedTarget屬性,但提供相似的屬性。
在mouseover事件觸發時,IE的fromElement屬性中保存着相關元素;在mouseout事件觸發時,IE的toElement屬性中保存了相關元素。
IE9支持所有的這些屬性。
將getRelatedTarge()方法添加到event_util對象中。
1 getRelatedTarget: function(event) { 2 if(event.relatedTarget) { 3 return event.relatedTarget; 4 } else if(event.fromElement) { 5 return event.fromElement; 6 } else if(event.toElement) { 7 return event.toElement; 8 } else { 9 return null; 10 } 11 }
附上完整的event_util對象的代碼:

1 var event_util = { 2 //添加事件 3 addHandler: function(element, type, handler) { 4 if(element.addEventListener) { 5 element.addEventListener(type, handler, false); 6 } else if(element.attachEvent) { 7 element.attachEvent("on" + type, handler); 8 } else { 9 element["on" + type] = handler; 10 } 11 }, 12 //移除事件 13 removeHandler: function(element, type, handler) { 14 if(element.removeEventListener) { 15 element.removeEventListener(type, handler, false); 16 } else if(element.detachEvent) { 17 element.detachEvent("on" + type, handler); 18 } else { 19 element["on" + type] = null; 20 } 21 }, 22 //獲取事件對象 23 getEvent: function(event) { 24 return event ? event : window.event; 25 }, 26 //獲取事件目標 27 getTarget: function(event) { 28 return event.target || event.srcElement; 29 }, 30 //阻止事件冒泡 31 stopPropagation: function(event) { 32 if(event.stopPropagation) { 33 event.stopPropagation(); 34 } else { 35 event.cancelBubble = true; 36 } 37 }, 38 //取消事件默認行為 39 preventDefault: function(event) { 40 if(event.preventDefault) { 41 event.preventDefault(); 42 } else { 43 event.returnValue = false; 44 } 45 }, 46 //獲取相關元素 47 getRelatedTarget: function(event) { 48 if(event.relatedTarget) { 49 return event.relatedTarget; 50 } else if(event.fromElement) { 51 return event.fromElement; 52 } else if(event.toElement) { 53 return event.toElement; 54 } else { 55 return null; 56 } 57 } 58 }
相關元素的示例:
1 var btn = document.getElementById("btn"); 2 event_util.addHandler(btn, "mouseover", function(event) { 3 event = event_util.getEvent(event); 4 var relatedTarget = event_util.getRelatedTarget(event); 5 var target = event_util.getTarget(event); 6 alert("光標從" + relatedTarget.tagName + "移動到" + target.tagName); 7 });
注意:Javascript中的tagName都是大寫。
鼠標按鈕
對於mousedown和mouseup事件而言,在event事件對象中存在一個button屬性,表示按下或釋放的按鈕。
DOM中的button屬性可能有以下三個值:0,表示主鼠標按鈕;1,表示中間按鈕;2,表示次鼠標按鈕。
IE8及之前版本中,也提供button屬性,但其屬性值與DOM的不太通屬性值有很大差別。
再為event_util對象添加getButton()方法。
1 getButton: function(event) { 2 if(document.implementation.hasFeature("MouseEvents", "2.0")) { 3 return event.button; 4 } else { 5 switch(event.button) { 6 case 0: 7 case 1: 8 case 3: 9 case 5: 10 case 7: 11 return 0; 12 case 2: 13 case 6: 14 return 2; 15 case 4: 16 return 1; 17 } 18 } 19 }
通過檢測“MouseEvents”這個特性,就可以確定event對象中存在的button屬性中是否包含正確的值;如果測試失敗,說明是IE,就必須對相應的值進行規范化。
附上event_util對象的完整代碼:

1 var event_util = { 2 //添加事件 3 addHandler: function(element, type, handler) { 4 if(element.addEventListener) { 5 element.addEventListener(type, handler, false); 6 } else if(element.attachEvent) { 7 element.attachEvent("on" + type, handler); 8 } else { 9 element["on" + type] = handler; 10 } 11 }, 12 //移除事件 13 removeHandler: function(element, type, handler) { 14 if(element.removeEventListener) { 15 element.removeEventListener(type, handler, false); 16 } else if(element.detachEvent) { 17 element.detachEvent("on" + type, handler); 18 } else { 19 element["on" + type] = null; 20 } 21 }, 22 //獲取事件對象 23 getEvent: function(event) { 24 return event ? event : window.event; 25 }, 26 //獲取事件目標 27 getTarget: function(event) { 28 return event.target || event.srcElement; 29 }, 30 //阻止事件冒泡 31 stopPropagation: function(event) { 32 if(event.stopPropagation) { 33 event.stopPropagation(); 34 } else { 35 event.cancelBubble = true; 36 } 37 }, 38 //取消事件默認行為 39 preventDefault: function(event) { 40 if(event.preventDefault) { 41 event.preventDefault(); 42 } else { 43 event.returnValue = false; 44 } 45 }, 46 //獲取相關元素 47 getRelatedTarget: function(event) { 48 if(event.relatedTarget) { 49 return event.relatedTarget; 50 } else if(event.fromElement) { 51 return event.fromElement; 52 } else if(event.toElement) { 53 return event.toElement; 54 } else { 55 return null; 56 } 57 }, 58 //獲取button屬性值 59 getButton: function(event) { 60 if(document.implementation.hasFeature("MouseEvents", "2.0")) { 61 return event.button; 62 } else { 63 switch(event.button) { 64 case 0: 65 case 1: 66 case 3: 67 case 5: 68 case 7: 69 return 0; 70 case 2: 71 case 6: 72 return 2; 73 case 4: 74 return 1; 75 } 76 } 77 } 78 }
鼠標按鈕示例:
1 event_util.addHandler(document, "click", function(event) { 2 event = event_util.getEvent(event); 3 alert(event.button); 4 });
更多的事件信息
對於鼠標事件來說,detail屬性中包含一個數值,表示在給定位置上發生多少次單擊;在同一個位置上相繼發生mousedown和mouseup事件算作一次單擊。
detail從1開始計數。
如果鼠標在mousedown和mouseup事件之間移動了位置,則detail的值會被重置為0。
鼠標滾輪事件
mousewheel事件,可以在任何元素上觸發,最終會冒泡到document(IE8)或window(IE9、Opera、Chrome和Safari)對象。
與mousewheel事件對應的event對象除了包含鼠標事件的所有標准信息之外,還包含一個特殊的wheelDelta屬性;當用戶向前滾動鼠標滾輪時,wheelDelta是120的倍數,當用戶向后滾動鼠標滾輪時,wheelDelta是-120的倍數。
1 event_util.addHandler(document, "mousewheel", function(event) { 2 event = event_util.getEvent(event); 3 alert(event.wheelDelta); 4 });
多數情況下,只需要知道滾動鼠標滾輪的方向,而這通過檢測wheelDelta的正負號就可以確定。
在Opera9.5及之前版本中,wheelDelta的正負號是顛倒的;可以使用瀏覽器檢測技術來確定實際的值,如下:
1 event_util.addHandler(document, "mousewheel", function(event) { 2 event = event_util.getEvent(event); 3 var delta = (client.engine.opera && client.engine.opera < 9.5 ? -event.wheelDelta : event.wheelDelta) 4 alert(delta); 5 });
在Firefox瀏覽器中,支持一個名為DOMMouseScroll的事件,也是在鼠標滾輪滾動時觸發,其被視為鼠標事件,包含與鼠標事件有關的所有信息;而有關鼠標滾輪的信息則保存在detail屬性中,當向前滾動滾輪時,其值為-3的倍數,當向后滾動滾輪時,其值為3的倍數。
可以將DOMMouseScroll事件添加到頁面中的任何元素,而且該事件會冒泡到window對象。
1 event_util.addHandler(document, "DOMMouseScroll", function(event) { 2 event = event_util.getEvent(event); 3 alert(event.detail); 4 });
跨瀏覽器的解決方案
首先,創建一個能夠取得鼠標滾輪增量值(delta)的方法getWheelDelta()。
1 getWheelDelta: function(event) { 2 if(event.wheelDelta) { 3 return event.wheelDelta; 4 } else { 5 return -event.detail * 40; 6 } 7 }
首先檢測事件中是否包含wheelDelta屬性,如果是,則通過wheelDelta屬性返回值;如果不存在wheelDelta屬性,則假設相應的值保存在detail屬性中,將這個值取反然后乘以40,就可以得到與其它瀏覽器相同的值。
上述代碼暫不考慮Opera9.5及之前的情況。
將這個方法添加到event_util對象中;附上event_util對象完整代碼:

1 var event_util = { 2 //添加事件 3 addHandler: function(element, type, handler) { 4 if(element.addEventListener) { 5 element.addEventListener(type, handler, false); 6 } else if(element.attachEvent) { 7 element.attachEvent("on" + type, handler); 8 } else { 9 element["on" + type] = handler; 10 } 11 }, 12 //移除事件 13 removeHandler: function(element, type, handler) { 14 if(element.removeEventListener) { 15 element.removeEventListener(type, handler, false); 16 } else if(element.detachEvent) { 17 element.detachEvent("on" + type, handler); 18 } else { 19 element["on" + type] = null; 20 } 21 }, 22 //獲取事件對象 23 getEvent: function(event) { 24 return event ? event : window.event; 25 }, 26 //獲取事件目標 27 getTarget: function(event) { 28 return event.target || event.srcElement; 29 }, 30 //阻止事件冒泡 31 stopPropagation: function(event) { 32 if(event.stopPropagation) { 33 event.stopPropagation(); 34 } else { 35 event.cancelBubble = true; 36 } 37 }, 38 //取消事件默認行為 39 preventDefault: function(event) { 40 if(event.preventDefault) { 41 event.preventDefault(); 42 } else { 43 event.returnValue = false; 44 } 45 }, 46 //獲取相關元素 47 getRelatedTarget: function(event) { 48 if(event.relatedTarget) { 49 return event.relatedTarget; 50 } else if(event.fromElement) { 51 return event.fromElement; 52 } else if(event.toElement) { 53 return event.toElement; 54 } else { 55 return null; 56 } 57 }, 58 //獲取button屬性值 59 getButton: function(event) { 60 if(document.implementation.hasFeature("MouseEvents", "2.0")) { 61 return event.button; 62 } else { 63 switch(event.button) { 64 case 0: 65 case 1: 66 case 3: 67 case 5: 68 case 7: 69 return 0; 70 case 2: 71 case 6: 72 return 2; 73 case 4: 74 return 1; 75 } 76 } 77 }, 78 //獲取鼠標滾輪增量值 79 getWheelDelta: function(event) { 80 if(event.wheelDelta) { 81 return event.wheelDelta; 82 } else { 83 return -event.detail * 40; 84 } 85 } 86 }
定義一個handleWheelDelta函數,作為一個事件處理程序,可以同時應對FIrefox中的“DOMMouseScroll”事件和非Firefox中的“mousewheel”事件。
1 function handleMouseWheel(event) { 2 event = event_util.getEvent(event); 3 var delta = event_util.getWheelDelta(event); 4 alert(delta); 5 }
可以使用以下代碼來添加事件:
1 event_util.addHandler(document, "DOMMouseScroll", handleMouseWheel); 2 event_util.addHandler(document, "mousewheel", handleMouseWheel);
可以進一步將這些代碼放在一個私有作用域中,從而不會讓新定義的函數干擾全局作用域。
1 (function() { 2 function handleMouseWheel(event) { 3 event = event_util.getEvent(event); 4 var delta = event_util.getWheelDelta(event); 5 alert(delta); 6 } 7 event_util.addHandler(document, "DOMMouseScroll", handleMouseWheel); 8 event_util.addHandler(document, "mousewheel", handleMouseWheel); 9 })();
觸摸設備
(1)不支持dbclick事件。雙擊瀏覽器窗口會放大畫面,而且沒有辦法改變該行為。
(2)輕擊可單擊元素會觸發mouseover事件。如果此操作導致內容變化,則不會再有其它事件發生;如果屏幕沒有發生變化,則依次發生mousedown、mouseup、click事件。
(3)mousemove事件也會觸發mouseover和mouseout事件。
(4)兩個手指放在屏幕上且頁面隨手指移動而滾動時會觸發mousewheel和scroll事件。
無障礙性問題
可以通過鍵盤上的回車鍵來觸發click事件,而其它鼠標事件都不能通過鍵盤觸發;因此,不建議使用其它鼠標事件來展示功能或引發代碼執行,因為這樣會給屏幕閱讀器用戶造成極大不便。
使用鼠標事件時應注意的幾個易訪問性問題:
(1)使用click事件執行代碼。
(2)不要使用mouseover向用戶顯示新的選項;如果確實需要通過這種方式來顯示,可以考慮添加顯示相同信息的鍵盤快捷方式。
(3)不要使用dbclick執行重要的操作;鍵盤無法觸發這個事件。