事件流 事件冒泡 IE的事件流叫做事件冒泡,即事件開始時由最具體的元素接收,然後逐級向上傳播到較為不具體的節點(文檔)。如下代碼: 如果你點擊了上面的div元素,那麼這個click事件會按照如下順序傳播: (1)<div> (2)<body> (3)<html> (4)document 所有現代瀏覽 ...
事件流
事件冒泡
IE的事件流叫做事件冒泡,即事件開始時由最具體的元素接收,然後逐級向上傳播到較為不具體的節點(文檔)。如下代碼:
<body> <div id="myDiv">click me</div> </body>
如果你點擊了上面的div元素,那麼這個click事件會按照如下順序傳播:
- (1)<div>
- (2)<body>
- (3)<html>
- (4)document
所有現代瀏覽器都支持事件冒泡,但在具體實現上還是有一些差別。IE5.5以及更早的版本事件冒泡會跳過<html>元素(從<body>直接跳到document)。IE9及其它瀏覽器則將事件一直冒泡到window對象。
事件捕獲
事件捕獲的思想是不太具體的節點應該更早接收到事件,而最具體的節點應該最後接收到事件。如上面的例子,單機div元素,那麼就會按照以下順序觸發click事件:
- (1)document
- (2)<html>
- (3)<body>
- (4)<div>
由於老版本瀏覽器的不支持,因此很少有人使用事件捕獲。
DOM事件流
“DOM2級事件”規定的事件流包括三個階段:事件捕獲階段、處於目標階段和事件冒泡階段。
事件處理程式
HTML事件處理程式
<input type="button" value="click me" onclick="alert(this.value)"/>
DOM0級事件處理程式
var btn = document.getElementById('myBtn'); btn.onclick = function(){ console.log(this.value); }
DOM2級事件處理程式
“DOM2級事件”定義了兩個方法,用於處理指定和刪除事件處理程式的操作:addEventListener()和removeEventListener()。接受三個參數:要處理的事件名、作為事件處理程式的函數和一個布爾值。布爾值為true,表示在捕獲階段調用事件處理程式;如果為false,表示在冒泡階段調用事件處理程式。
var btn = document.getElementById('myBtn'); btn.addEventListener('click',function(){ alert(this.value) },false)
IE事件處理程式
IE中實現了與DOM中類似的兩個方法:attachEvent()和detachEvent()。這兩個方法接受相同的兩個參數:事件處理程式名稱和事件處理程式函數。由於IE8及更早版本只支持事件冒泡,所以通過attachEvent()添加的事件處理程式都會被添加到冒泡階段。
btn.attachEvent('onclick',function(){ alert(this.value) //undefined })
結果為undefined的原因是,在使用attachEvent()方法的情況下,事件處理程式會在全局作用域中運行。因此this等於window。在編寫跨瀏覽器區別的時候,牢記這一區別非常重要。
attachEvent()方法也可以為一個元素添加多個事件處理程式,如下:
btn.attachEvent('onclick',function(){ alert('clicked') }) btn.attachEvent('onclick',function(){ alert('hello world!') })
在IE9以及更改版本瀏覽器得到的結果順序跟addEventListener()的順序一樣,結果是先clicked,後hello world!。在IE8以及以前版本得到順序是相反的。
跨瀏覽器的事件處理程式
var EventUtil = { addHandler:function(element,type,handler){ if(element.addEventListener){ element.addEventListener(type,handler,false); }else if(element.attachEvent){ element.attachEvent("on" + type,handler); }else{ element["on" + type] = handler; } }, removeHandler:function(element,type,handler){ if(element.removeEventListener){ element.removeEventListener(type,handler,false); }else if(element.detachEvent){ element.detachEvent("on" + type,handler); }else{ element["on" + type] = null; } } }
調用方式:
var handler = function(){ alert('clicked'); } EventUtil.addHandler(btn,"click",handler); //這裡省略其它代碼 EventUtil.removeHandler(btn,"click",handler);
事件對象
DOM中的事件對象
相容DOM的瀏覽器會將一個event對象傳入到事件處理程式中,無論指定事件處理程式時用什麼方法(DOM0級或DOM2級),都會傳入event對象。下麵的例子:
var btn = document.getElementById('myBtn'); btn.onclick = function(event){ console.log(event.type); //click } btn.addEventListener('click',function(event){ console.log(event.type); //click },false)
在通過HTML特性指定事件處理程式時,變數event中保存著event對象,下麵例子:
<input type="button" value="click me" onclick="alert(event.type)"/>
event對象包含與創建它的特定事件有關的屬性和方法。觸發的事件類型不一樣,可用的屬性和方法也不一樣。不過,所有事件都會有下表列出的成員:
- bubbles:(Boolean)只讀,表明事件是否冒泡。
- cancelable:(Boolean)只讀,表明是否可以取消事件的預設行為。
- currentTarget:(Element)只讀,其事件處理程式當前正在處理事件的那個元素。
- defaultPrevented:(Boolean)只讀,為true表明已經調用了preventDefault()(DOM3級事件中新增)。
- detail:(Integer)只讀,與事件相關的細節信息。
- eventPhase:(Integer)只讀,調用事件處理程式的階段:1表示捕獲階段,2表示“處於目標”,3表示冒泡階段。
- preventDafult():(Function)只讀,取消事件的預設行為。如果cancelable是true,則可以使用這個方法。
- stopImmediatePropagation():(Function)只讀,取消事件的進一步捕獲或冒泡,同時阻止任何事件處理程式被調用(DOM3級事件中新增)。
- stopPropagation():(Function)只讀,取消事件的進一步捕獲或冒泡。如果過bubbles為true,則可以使用這個方法。
- target:(Element)只讀,事件的目標。
- trusted:(Boolean)只讀,為true表示事件是瀏覽器生成的,為false表示事件是有開發人員通過JavaScript創建的(DOM3級事件中新增)。
- type:(String)只讀,被觸發的事件的類型。
- view:(AbstractView)只讀,與事件關聯的抽象視圖。等同於發生事件的window對象。
在事件處理程式內部,對象this始終等於currentTarget的值,而target只包含事件的實際目標。如果直接將事件處理程式指向給了目標元素,則this,currentTarget和target包含相同的值,如下代碼:
var btn = document.getElementById('myBtn'); btn.addEventListener('click',function(event){ console.log(event.currentTarget === this); //true console.log(event.target === this); //true },false)
如果事件處理程式存在於按鈕的父節點中(例如document.body),那麼這些值是不相等的,如下代碼:
var btn = document.getElementById('myBtn'); document.body.addEventListener('click',function(event){ console.log(event.currentTarget === document.body); //true console.log(document.body === this); //true console.log(event.target === btn); //true },false)
在需要一個函數處理多個事件時,可以使用type屬性,如下:
var btn = document.getElementById('myBtn'); var handler = function(event){ switch(event.type){ case "click": console.log("clicked"); break; case "mouseover": event.target.style.backgroundColor = "red"; break; case "mouseout": event.target.style.backgroundColor =""; break; } }; btn.onclick = handler; btn.onmouseover = handler; btn.onmouseout = handler;
阻止事件的預設行為,使用preventDefault()方法,比如阻止a標簽的跳轉,如下代碼:
var link = document.getElementById('myLink'); link.onclick = function(event){ event.preventDefault(); }
只有cancelable屬性設置為true時,才可以使用preventDefault()來取消其預設行為。
stopPropagation()方法用於立即停止事件在DOM層次中的傳播,寄取消進一步的事件捕獲或冒泡。例如直接添加到一個按鈕的事件處理程式可以調用stopPropagation(),從而避免觸發註冊在document.body上面的事件處理程式,如下代碼:
var btn = document.getElementById('myBtn'); btn.onclick = function(event){ console.log('clicked'); event.stopPropagation(); } document.body.onclick = function(){ console.log('body clicked'); }
結果只列印出了clicked。
事件對象的eventPhase屬性,可以用來確定事件當前正處理事件流的哪個階段。1表示捕獲階段,2表示“處於目標”,3表示冒泡階段。
var btn = document.getElementById('myBtn'); btn.onclick = function(event){ console.log(event.eventPhase);//2 }; document.body.addEventListener('click',function(event){ console.log(event.eventPhase);//1 },true); document.body.onclick = function(){ console.log(event.eventPhase);//3 };
顯示結果順序分別為1,2,3。
IE中的事件對象
與訪問DOM中的event對象不同,要訪問IE中的event對象有幾種不同的方式,取決於事件處理程式的方法。在使用DOM0級方法添加事件處理程式時,event對象作為window對象的一個屬性存在,如下例子:
var btn = document.getElementById('myBtn'); btn.onclick = function(){ var event = window.event; alert(event.type); //click };
如果事件程式是使用attachEvent()添加的,那麼就會有一個event對象作為參數被傳遞到事件處理程式函數中,如下代碼:
var btn = document.getElementById('myBtn'); btn.attachEvent('onclick',function(event){ alert(event.type); //click })
IE中的event對象包含下麵的屬性和方法:
- cancelBubble:(Boolean)讀/寫,預設值為false,將其設置為true就可以取消事件冒泡(與DOM中的stopPropagation()方法的作用相同)。
- returnValue:(Boolean)讀/寫,預設值為true,將其設置為false可以取消事件的預設行為(與DOM中的preventDafult()方法的作用相同)。
- srcElement:(Element)只讀,事件的目標(與DOM中的target屬性相同)。
- type:(String)只讀,被觸發的事件的類型。
因為事件處理程式中的作用域是根據指定它的方式來確定的,所以不能認為this會始終等於事件目標。故而,最好還是使用event.srcElement比較保險,如下代碼:
var btn = document.getElementById('myBtn'); btn.onclick = function(){ alert(window.event.srcElement === this); //true }; btn.attachEvent('onclick',function(event){ alert(this); //window alert(window.event.srcElement === this); //false })
取消預設行為:
var link = document.getElementById('myLink'); link.onclick = function(){ window.event.returnValue = false; };
停止事件冒泡:
var btn = document.getElementById('myBtn'); btn.onclick = function(){ alert('clicked'); window.event.cancelBubble = true; }; document.body.attachEvent('onclick',function(event){ alert('body clicked') })
跨瀏覽器的事件對象
var EventUtil = { //事件處理程式 addHandler:function(element,type,handler){ if(element.addEventListener){ element.addEventListener(type,handler,false); }else if(element.attachEvent){ element.attachEvent("on" + type,handler); }else{ element["on" + type] = handler; } }, //得到event對象 getEvent:function(event){ return event ? event : window.event; }, //得到事件目標 getTarget:function(event){ return event.target || event.srcElement; }, //取消預設行為 preventDefault:function(event){ if(event.preventDefault){ event.preventDefault(); }else{ event.returnValue = false; } }, //移除事件處理程式 removeHandler:function(element,type,handler){ if(element.removeEventListener){ element.removeEventListener(type,handler,false); }else if(element.detachEvent){ element.detachEvent("on" + type,handler); }else{ element["on" + type] = null; } }, //阻止事件捕獲或冒泡 stopPropagation:function(event){ if(event.stopPropagation){ event.stopPropagation(); }else{ event.cancelBubble = true; } } }
事件類型
“DOM3級事件”規定如下幾類事件。
- UI(User Interface,用戶界面)事件,當用戶與頁面上的元素交互時觸發;
- 焦點事件,當元素獲得或者失去焦點時觸發;
- 滑鼠事件,當用戶通過滑鼠在頁面上執行操作時觸發;
- 滾輪事件,當滑鼠使用滾輪(或類似設備)時觸發;
- 文本事件,當在文檔中輸入文本時觸發;
- 鍵盤事件,當用戶通過鍵盤在頁面上執行操作時觸發;
- 合成事件,當為IME(Input Method Editor,輸入法編輯器)輸入字元時觸發;
- 變動(mutation)事件,當底層DOM結構發生變化時觸發;
- 變動名稱事件,當元素或屬性名變動時觸發。此類事件已經廢除。
UI事件
- DOMActivate:表示元素已經被用戶操作(通過滑鼠或鍵盤)激活。這個事件在DOM3級事件中被廢除,但firefox2+和Chrome支持它。
- load:當頁面完全載入後在window上面觸發,當所有框架都載入完畢時在框架集上面觸發,當圖像載入完畢時在<img>元素上面觸發,或者當嵌入的內容載入完畢時在<object>元素上面觸發。
- unload:當頁面完全卸載後在window上面觸發,當所有框架都卸載後在框架集上面觸發,或者當嵌入的內容卸載完畢後早<object>元素上面觸發。
- abort:在用戶停止下載過程時,如果嵌入的內容沒有載入完,則在<object>元素上面觸發。
- error:當發送JavaScript錯誤時在window上面觸發,當無法載入圖片是在<img>元素上面觸發,當無法載入嵌入內容時在<object>元素上面觸發,或者當有一或多個框架無法載入時在框架集上面觸發。
- select:當用戶選擇文本框(<input>或<textarea>)中的一或多個字元時觸發。
- resize:當視窗或框架的大小發生變化時在window或框架上面觸發。
- scroll:當用戶滾動帶滾動條的元素中的內容時,在該元素上觸發。<body>元素中包含所載入頁面的滾動條。
多數這些事件都與window對象或者表單控制項相關。
除了DOMActivate之外,其它事件在DOM2級事件中都歸為HTML事件(DOMActivate在DOM2級事件中仍屬於UI事件)。要確定瀏覽器是否支持DOM2級事件規定的HTML事件,可以用如下代碼:
var isSupported = document.implementation.hasFeature("HTMLEvents","2.0");
確定瀏覽器是否支持DOM3級事件定義的事件,代碼如下:
var isSupported = document.implementation.hasFeature("UIEvent","3.0");
1.load事件
EventUtil.addHandler(window,'load',function(event){ console.log('loaded!'); })
為<body>元素添加一個onload特性,代碼如下:
<body onload="alert('loaded!')"> </body>
一般在window上面發生的任何事件都可以在<body/>元素中通過相應的特性來指定,因為在HTML中無法訪問到window元素。建議儘可能使用JavaScript方式。
圖片載入:
var image = document.getElementById('myImage'); EventUtil.addHandler(image,'load',function(event){ event = EventUtil.getEvent(event); console.log(EventUtil.getTarget(event).src); })
待創建新的<img>元素時,可以為其指定一個事件處理程式。此時,最重要的是在指定src屬性之前先指定事件,如下代碼:
EventUtil.addHandler(window,'load',function(){ var image = document.createElement('img'); EventUtil.addHandler(image,'load',function(event){ event = EventUtil.getEvent(event); console.log(EventUtil.getTarget(event).src); }) document.body.appendChild(image); image.src = 'images/b.jpg'; })
需要格外註意的一點是:新圖像元素不一定要從添加到文檔後才開始下載,只要設置了src屬性就開始下載。
同樣的功能可以使用DOM0級的Image對象實現,如下代碼:
EventUtil.addHandler(window,'load',function(){ var image = new Image(); EventUtil.addHandler(image,'load',function(event){ event = EventUtil.getEvent(event); console.log(EventUtil.getTarget(event).src); }) image.src = 'images/b.jpg'; })
還有一些元素以非標準的的方式支持load事件。在IE9以及更高版本,<script>元素也會觸發load事件。
EventUtil.addHandler(window,'load',function(){ var script = document.createElement('script'); EventUtil.addHandler(script,'load',function(event){ console.log('loaded!'); }) script.src = 'js/common.js'; document.body.appendChild(script); })
註:IE8以及更早版本不支持<script>元素上的load事件。
<link>元素的load事件:
EventUtil.addHandler(window,'load',function(){ var link = document.createElement('link'); link.type ="text/css"; link.rel ="stylesheet"; EventUtil.addHandler(link,'load',function(event){ console.log('loaded!'); }) link.href = 'css/rest.css'; document.getElementsByTagName('head')[0].appendChild(link) })
與<script>節點類似,在未指定href屬性並將<link>元素添加到文檔之前也不會開始下載樣式表。
2.unload事件
這個事件在文檔完全被卸載後觸發。只要用戶從一個頁面切換到另一個頁面,就會發生unload事件。而利用這個事件最多的情況就是清除引用,以避免記憶體泄露。
EventUtil.addHandler(window,'unload',function(){ alert('unloaded!'); })
3.resize事件
EventUtil.addHandler(window,'resize',function(){ alert('resized!'); })
4.scroll事件
EventUtil.addHandler(window,'scroll',function(){ if(document.compatMode == 'CSS1Compat'){ console.log(document.documentElement.scrollTop); }else{ console.log(document.body.scrollTop); } })
焦點事件
焦點事件會在頁面獲得或者失去焦點時觸發。利用這些事件並與document.hasFocus()方法以及document.activeElement屬性配合,可以知曉用戶在頁面中的行蹤,以下6個焦點事件。
- blur:在元素失去焦點時觸發。這個事件不會冒泡;所有瀏覽器都支持它。
- DOMFocusIn:在元素獲得焦點時觸發。這個事件與HTML事件focus等價,但它冒泡。只有opera支持這個事件。DOM3級事件廢除了DOMFocusIn,選擇了focusin。
- DOMFocusOut:在元素失去焦點時觸發。這個事件是HTML事件blur的通用版本。只有opera支持這個事件。DOM3級事件廢除了DOMFocusOut,選擇了focusout。
- focus:在元素獲得焦點時觸發。這個事件不會冒泡;所有瀏覽器都支持它。
- focusin:在元素獲得焦點時觸發。這個事件與HTML事件focus等價,但它冒泡。支持這個事件的瀏覽器有IE5.5+、Safari5.1+、Opera11.5+和Chrome。
- focusout:在元素失去焦點時觸發。這個事件是HTML事件blur的通用版本。支持這個事件的瀏覽器有IE5.5+、Safari5.1+、Opera11.5+和Chrome。
IE的focusin和focusout最後被DOM3級事件採納為標準方式。
當焦點從頁面中的一個元素移動到另一個元素,會依次觸發下列事件:
(1)focusout在失去焦點的元素上觸發。
(2)focusin在獲得焦點的元素上觸發。
(3)blur在失去焦點的元素上觸發。
(4)DOMFocusOut在失去焦點的元素上觸發。
(5)focus在獲得焦點的元素上觸發。
(6)DOMFocusIn在獲得焦點的元素上觸發。
確定瀏覽器是否支持這些事件,可以使用如下代碼:
var isSupported = document.implementation.hasFeature('FocusEvent','3.0');
滑鼠與滾輪事件
DOM3級事件中定義了9個滑鼠事件,如下:
- click:在用戶單機主滑鼠按鈕(一般是左邊的按鈕)或者按下回車鍵時觸發。
- dblclick:在用戶雙擊主滑鼠按鈕(一般是左邊的按鈕)時觸發。DOM3級納入了標準。
- mousedown:在用戶按下任意滑鼠按鈕時觸發。不能通過鍵盤觸發這個事件。
- mouseenter:在滑鼠游標從元素外部首次移動到元素範圍之內時觸發。這個事件不冒泡,而且在游標移動到後代元素上不會觸發。DOM3級事件將它納入了規範。
- mouseleave:在位於元素上方的滑鼠游標移動到元素範圍之外時觸發。這個事件不冒泡,而且在游標移動到後代元素上不會觸發。DOM3級事件將它納入了規範。
- 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
IE8及之前版本的實現有一個小bug,因此在雙擊事件中,會跳過第二個mousedown和click事件,其順序如下:
(1)mousedown
(2)mouseup
(3)click
(4)mouseup
(5)dblclick
IE9修複了這個bug。
使用如下代碼可以檢測瀏覽器是否支持如上DOM2級事件(除dblckick、mouseenter和mouseleave之外):
var isSupported = document.implementation.hasFeature('MouseEvents','2.0');
檢測瀏覽器是否支持上面的所有事件,代碼如下:
var isSupported = document.implementation.hasFeature('MouseEvent','3.0');
1.客戶區坐標位置
clientX和clientY他們的值表示事件發生時滑鼠指針在視口中的水平和垂直坐標。
var btn = document.getElementById('myBtn'); EventUtil.addHandler(btn,'click',function(event){ event = EventUtil.getEvent(event); console.log("client coordinates:" + event.clientX + "," + event.clientY); })
2.頁面坐標位置
pageX和pageY,這兩個屬性表示滑鼠游標在頁面中的位置,因此坐標是從頁面本身而非視口的左邊和頂邊計算的。
以下代碼可以取得滑鼠事件在頁面中的坐標:
var btn = document.getElementById('myBtn'); EventUtil.addHandler(btn,'click',function(event){ event = EventUtil.getEvent(event); console.log("Page coordinates:" + event.pageX + "," + event.pageY); })
在頁面沒有滾動的情況下,pageX和pageY的值與clientX和clientY的值相等。
IE8及更早的版本不支持事件對象上的頁面坐標,不過可以使用客戶區坐標和滾動信息可以計算出來。這時候需要用到document.body(混雜模式)或document.documentElement(標準模式)中的scrollLeft和scrollTop屬性。計算代碼如下:
var btn = document.getElementById('myBtn'); EventUtil.addHandler(btn,'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); } console.log("Page coordinates:" + pageX + "," + pageY); })
3.屏幕坐標位置
screenX和screenY屬性表示滑鼠事件發生時滑鼠指針相對於整個屏幕的坐標信息。
屏幕坐標:
var btn = document.getElementById('myBtn'); EventUtil.addHandler(btn,'click',function(event){ event = EventUtil.getEvent(event); console.log("Page coordinates:" + event.screenX + "," + event.screenY); })
4.修改鍵
雖然滑鼠事件主要是由滑鼠來觸發的,但在按下滑鼠時鍵盤上的某些鍵的狀態也可以影響到所要採取的操作。這些修改鍵就是Shift、Ctrl、Alt和Meta(在windows鍵盤中的windows鍵,在蘋果機中是Cmd鍵),它們經常被用來修改滑鼠事件的行為。DOM為此規定了4個屬性,表示這些修改鍵的狀態:shiftKey、ctrlKey、altKey、metaKey。這些屬性中包含的都是布爾值,如果相應的鍵被按下了,則值為true,否則值為false。
當某個滑鼠事件發生時,通過檢測這幾個屬性可以確定是否用戶同時按下了其中的鍵。如下例子:
var btn = document.getElementById('myBtn'); EventUtil.addHandler(btn,'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'); } console.log("keys:" + keys.join(',')); })
註:IE8以及之前的版本不支持metaKey屬性。
5.相關元素
在發生mouseover和mouseout事件時,還會涉及更多的元素。這兩個事件都會涉及把滑鼠指針從一個元素的邊界之內移動到另一個元素的邊界之內。對mouseover而言,事件的主目標是獲得游標的元素,而相關元素就是那個失去游標的元素。類似地,對於mouseout事件而言,事件的主目標就是失去游標的元素,而相關元素是獲得游標的元素。來看下麵一個例子:
<body> <div id="myDiv" style="background-color:red;width:100px;height:100px;"></div> </body>
這個例子會在頁面上顯示一個<div>元素。如果滑鼠指針一開始就在這個<div>元素上,然後移出了這個元素,那麼就會在<div>元素上觸發mouseout事件,相關元素就是<body>元素。與此同時,<body>元素上面會觸發mouseenter事件,相關元素就變成了<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; } }, //省略了其它代碼 };
調用:
var myDiv = document.getElementById('myDiv'); EventUtil.addHandler(myDiv,'mouseout',function(event){ event = EventUtil.getEvent(event); var target = EventUtil.getTarget(event); var relateTarget = EventUtil.getRelatedTarget(event); console.log("moused out of" + target.tagName + "to" + relateTarget.tagName); //moused out ofDIVtoBODY })
6.滑鼠按鈕
只有在主滑鼠按鈕被單擊(或鍵盤迴車鍵被按下)時才會觸發click事件,因此檢測按鈕的信息不是必要的。但對於mousedown和mouseup事件來說,則在其event對象存在一個button屬性,表示按下或者釋放的按鈕。DOM的button屬性可能有如下3個值:
- 0:表示主滑鼠按鈕;
- 1:表示中間的滑鼠按鈕(滑鼠滾輪按鈕);
- 2:表示次滑鼠按鈕;
在常規的設置中,主滑鼠按鈕就是滑鼠左鍵,而次滑鼠按鈕就是滑鼠右鍵。
IE8及之前的版本也提供了button屬性,但這個屬性的值與DOM中的button屬性有很大的差異。
- 0:沒有按下按鈕;
- 1:表示按下了主滑鼠按鈕;
- 2:表示按下了次滑鼠按鈕;
- 3:表示同時按下了主次滑鼠按鈕;
- 4:表示按下了中間的滑鼠按鈕;
- 5:表示同時按下了主滑鼠按鈕和中間的滑鼠按鈕;
- 6:表示同時按下了次滑鼠按鈕和中間的滑鼠按鈕;
- 7:表示同時按下了三個滑鼠按鈕;
由於單獨使用能力檢測無法確定差異(兩種模型有同名的button屬性),因此必須另闢蹊徑。我們知道,支持DOM版滑鼠事件的瀏覽器課可以通過hasFearture()方法檢測,所有可以再為EventUtil對象添加getButton()方法:
//得到button屬性 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; } } },
調用:
var myDiv = document.getElementById('myDiv'); EventUtil.addHandler(myDiv,'mousedown',function(event){ event = EventUtil.getEvent(event); alert(EventUtil.getButton(event)); })
7.更多的事件信息
“DOM2級事件”規範在event對象中還提供了detail屬性,用於給出有關事件的更多信息。對於滑鼠事件來說,detail中包含了一個數值,表示在給定位置上發生了多少次單擊。在同一個像素上相繼的發生一次mousedown和一次mouseup事件算作一次單擊。detail屬性從1開始計數,每次單擊發生後都會遞增。如果滑鼠在mousedown和mouseup之間移動了位置,則detail會被重置為0。
IE也通過下列屬性為滑鼠事件提供了更多信息。
- altLeft:布爾值,表示是否按下了alt鍵。
- ctrlLeft:布爾值,表示是否按下了ctrl鍵。
- offsetX:游標相對於目標元素邊界的x坐標。
- offsetY:游標相對於目標元素邊界的y坐標。
- shiftLeft:布爾值,表示是否按下了shift鍵。
這些屬性用處不大,只有IE支持他們,另一方面他們提供的信息要麼沒有什麼用,要麼可以通過其他方式計算得來。
8.滑鼠滾輪事件
IE6.0首先實現了mousewheel事件。這個事件可以在任何元素上面觸發,最終會冒泡到document(IE8)或window(IE9、Opera、Chrome及Safari)對象。與mousewheel事件對應的event對象除了包含滑鼠事件的所有標準信息外,還包含一個特殊的wheelDelta屬性。當用戶向前滾動滑鼠滑輪時,wheelDelta是120的倍數;當用戶向後滾動滑鼠滑輪時,wheelDelta是-120的倍數。
EventUtil.addHandler(document,'mousewheel',function(event){ event = EventUtil.getEvent(event); console.log(event.wheelDelta); })
多數情況下,只需要知道滾輪的滾動方向就夠了,而這通過檢測wheelDelta的正負號就可以確定。
註意的是,在Opera9.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); console.log(delta); })
Firefox支持一個名為DOMMouseScroll的類似事件,也是在滑鼠滾輪滾動時觸發。滑鼠滾輪滾動信息保存在detail屬性中,當向前滾動滑鼠滾輪時,這個屬性的值為-3的整數倍,當向後滾動滑鼠滾輪時,這個屬性的值是3的整數倍。
EventUtil.addHandler(document,'DOMMouseScroll',function(event){ event = EventUtil.getEvent(event); console.log(event.detail); })
跨瀏覽器總結,添加到EventUtil對象中:
//取得滑鼠滾輪增量值(delta) getWheelDelta:function(event){ if(event.wheelDelta){ return event.wheelDelta; }else{ return -event.detail * 40; } },
調用方式:
(function(){ function handleMouseWheel(event){ event = EventUtil.getEvent(event); var delta = EventUtil.getWheelDelta(event); console.log(delta); }; EventUtil.addHandler(document,"mousewheel",handleMouseWheel); EventUtil.addHandler(window,"DOMMouseScroll",handleMouseWheel); })();
9.觸摸設備
IOS和Android的實現非常特別,因為這些設備沒有滑鼠。在面向iphone和ipad中的Safari開發時,要記住以下幾點:
- 不支持dblclick事件。雙擊瀏覽器視窗會放大頁面,而且沒有辦法改變該行為。
- 輕擊可單擊元素會觸發mousemove事件。如果此操作會導致內容變化,將不再有其他事件發生;如果屏幕沒有因此變化,那麼會依次發生mousedown、mouseup和click事件。輕擊不可單擊的元素不會觸發任何事件。可單擊的元素是指那些單擊可產生預設操作的元素(如鏈接),或者那些已經被指定了onclick事件處理程式的元素。
- mousemove事件也會觸發mouseover和mouseout事件。
- 兩個手指放在屏幕上且頁面隨手指移動而滾動時會觸發mousewheel和scroll事件。
10.無障礙性問題
如果你的web應用程式或者網站要確保殘疾人特別是那些使用屏幕閱讀器的人都能訪問,那麼在使用滑鼠事件時就要格外小心。前面提到過,可以通過鍵盤上的回車鍵來觸發click事件,但其他滑鼠事件卻無法通過鍵盤來觸發。為此,我們不建議使用click之外的其他滑鼠事件來展示功能或者引發代碼執行。因為這樣會給盲人或視障用戶造成極大不便。
鍵盤與文本事件
“DOM3級事件”為鍵盤事件制定了規範。有3個鍵盤事件如下:
- keydown:當用戶按下鍵盤上的任意鍵時觸發,而且按住不放的話,會重覆觸發該事件。
- keypress:當用戶按下鍵盤上的字元鍵時觸發,而且按住不放的話,會重覆觸發該事件。按下ESC鍵也會觸發該事件。Safari3.1之前的版本也會在用戶按下非字元鍵時觸發該事件。
- keyup:當用戶釋放鍵盤上的鍵時觸發。
只有一個文本事件:textInput。這個事件是對keypress的補充,用意是在將文本顯示給用戶之前更容易攔截文本。在文本插入文本框之前會觸發textInput事件。
在用戶按下鍵盤上的字元鍵時,鍵盤執行順序:keydo