13.4.3 鼠標與滾輪事件【JavaScript高級程序設計第三版】

鼠標事件是Web 開發中最經常使用的一類事件,畢竟鼠標仍是最主要的定位設備。DOM3 級事件中定義了9 個鼠標事件,簡介以下。
click:在用戶單擊主鼠標按鈕(通常是左邊的按鈕)或者按下回車鍵時觸發。這一點對確保易訪問性很重要,意味着onclick 事件處理程序既能夠經過鍵盤也能夠經過鼠標執行。html

  • 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個事件觸發的順序始終以下:web

  • (1) mousedown
  • (2) mouseup
  • (3) click
  • (4) mousedown
  • (5) mouseup
  • (6) click
  • (7) dblclick

顯然,click 和dblclick 事件都會依賴於其餘先行事件的觸發;而mousedown 和mouseup 則不受其餘事件的影響。
IE8 及以前版本中的實現有一個小bug,所以在雙擊事件中,會跳過第二個mousedown 和click事件,其順序以下:api

  • (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 屬性。計算過程以下所示:htm

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數組中。在事件處理程序的最後,有一個警告框將檢測到的鍵的信息顯示給用戶。

IE九、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(IE九、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 應用程序或網站時的易訪問性。

下載離線版教程:http://www.shouce.ren/api/view/a/15218

要了解如何在網頁中實現無障礙訪問的內容,請訪問www.webaim.org 和http://yaccessibilityblog.com/。
相關文章
相關標籤/搜索