IE的事件流叫作事件冒泡,即事件開始時由最具體的元素接收,而後逐級向上傳播到較爲不具體的節點(文檔)。以下代碼:css
<body> <div id="myDiv">click me</div> </body>
若是你點擊了上面的div元素,那麼這個click事件會按照以下順序傳播:html
全部現代瀏覽器都支持事件冒泡,但在具體實現上仍是有一些差異。IE5.5以及更早的版本事件冒泡會跳過<html>元素(從<body>直接跳到document)。IE9及其它瀏覽器則將事件一直冒泡到window對象。web
事件捕獲的思想是不太具體的節點應該更早接收到事件,而最具體的節點應該最後接收到事件。如上面的例子,單機div元素,那麼就會按照如下順序觸發click事件:windows
因爲老版本瀏覽器的不支持,所以不多有人使用事件捕獲。數組
「DOM2級事件」規定的事件流包括三個階段:事件捕獲階段、處於目標階段和事件冒泡階段。瀏覽器
<input type="button" value="click me" onclick="alert(this.value)"/>
var btn = document.getElementById('myBtn'); btn.onclick = function(){ console.log(this.value); }
「DOM2級事件」定義了兩個方法,用於處理指定和刪除事件處理程序的操做:addEventListener()和removeEventListener()。接受三個參數:要處理的事件名、做爲事件處理程序的函數和一個布爾值。布爾值爲true,表示在捕獲階段調用事件處理程序;若是爲false,表示在冒泡階段調用事件處理程序。緩存
var btn = document.getElementById('myBtn'); btn.addEventListener('click',function(){ alert(this.value) },false)
IE中實現了與DOM中相似的兩個方法:attachEvent()和detachEvent()。這兩個方法接受相同的兩個參數:事件處理程序名稱和事件處理程序函數。因爲IE8及更早版本只支持事件冒泡,因此經過attachEvent()添加的事件處理程序都會被添加到冒泡階段。app
btn.attachEvent('onclick',function(){ alert(this.value) //undefined })
結果爲undefined的緣由是,在使用attachEvent()方法的狀況下,事件處理程序會在全局做用域中運行。所以this等於window。在編寫跨瀏覽器區別的時候,牢記這一區別很是重要。框架
attachEvent()方法也能夠爲一個元素添加多個事件處理程序,以下:iphone
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的瀏覽器會將一個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對象包含與建立它的特定事件有關的屬性和方法。觸發的事件類型不同,可用的屬性和方法也不同。不過,全部事件都會有下表列出的成員:
在事件處理程序內部,對象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。
與訪問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對象包含下面的屬性和方法:
由於事件處理程序中的做用域是根據指定它的方式來肯定的,因此不能認爲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級事件」規定以下幾類事件。
多數這些事件都與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");
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>元素添加到文檔以前也不會開始下載樣式表。
這個事件在文檔徹底被卸載後觸發。只要用戶從一個頁面切換到另外一個頁面,就會發生unload事件。而利用這個事件最多的狀況就是清除引用,以免內存泄露。
EventUtil.addHandler(window,'unload',function(){ alert('unloaded!'); })
EventUtil.addHandler(window,'resize',function(){ alert('resized!'); })
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個焦點事件。
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個鼠標事件,以下:
頁面上的全部元素都支持鼠標事件。除了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');
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); })
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); })
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); })
雖然鼠標事件主要是由鼠標來觸發的,但在按下鼠標時鍵盤上的某些鍵的狀態也能夠影響到所要採起的操做。這些修改鍵就是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屬性。
在發生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 })
只有在主鼠標按鈕被單擊(或鍵盤迴車鍵被按下)時纔會觸發click事件,所以檢測按鈕的信息不是必要的。但對於mousedown和mouseup事件來講,則在其event對象存在一個button屬性,表示按下或者釋放的按鈕。DOM的button屬性可能有以下3個值:
在常規的設置中,主鼠標按鈕就是鼠標左鍵,而次鼠標按鈕就是鼠標右鍵。
IE8及以前的版本也提供了button屬性,但這個屬性的值與DOM中的button屬性有很大的差別。
因爲單獨使用能力檢測沒法肯定差別(兩種模型有同名的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)); })
「DOM2級事件」規範在event對象中還提供了detail屬性,用於給出有關事件的更多信息。對於鼠標事件來講,detail中包含了一個數值,表示在給定位置上發生了多少次單擊。在同一個像素上相繼的發生一次mousedown和一次mouseup事件算做一次單擊。detail屬性從1開始計數,每次單擊發生後都會遞增。若是鼠標在mousedown和mouseup之間移動了位置,則detail會被重置爲0。
IE也經過下列屬性爲鼠標事件提供了更多信息。
這些屬性用處不大,只有IE支持他們,另外一方面他們提供的信息要麼沒有什麼用,要麼能夠經過其餘方式計算得來。
IE6.0首先實現了mousewheel事件。這個事件能夠在任何元素上面觸發,最終會冒泡到document(IE8)或window(IE九、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); })();
IOS和Android的實現很是特別,由於這些設備沒有鼠標。在面向iphone和ipad中的Safari開發時,要記住如下幾點:
若是你的web應用程序或者網站要確保殘疾人特別是那些使用屏幕閱讀器的人都能訪問,那麼在使用鼠標事件時就要格外當心。前面提到過,能夠經過鍵盤上的回車鍵來觸發click事件,但其餘鼠標事件卻沒法經過鍵盤來觸發。爲此,咱們不建議使用click以外的其餘鼠標事件來展現功能或者引起代碼執行。由於這樣會給盲人或視障用戶形成極大不便。
「DOM3級事件」爲鍵盤事件制定了規範。有3個鍵盤事件以下:
只有一個文本事件:textInput。這個事件是對keypress的補充,用意是在將文本顯示給用戶以前更容易攔截文本。在文本插入文本框以前會觸發textInput事件。
在用戶按下鍵盤上的字符鍵時,鍵盤執行順序:keydown、keypress、keyup。其中keydown、keypress都是在文本框發生變化以前被觸發的;而keyup是在文本框已經發生變化後觸發。若是用戶按下一個字符鍵不放,那麼會重複觸發keydown與keypress,直到用戶鬆開該鍵爲止。
若是用戶按下的是一個非字符鍵時,執行順序:keydown、keyup。
在發生keydown與keyup事件時,event對象的keyCode屬性包含一個代碼,與鍵盤上一個特定的鍵對應。DOM和IE中的event對象都支持keyCode屬性。以下例子:
var textbox = document.getElementById('myText'); EventUtil.addHandler(textbox,'keyup',function(event){ event = EventUtil.getEvent(event); console.log(event.keyCode); })
經常使用非字符鍵的鍵碼:
左箭頭:37;上箭頭:38;右箭頭:39;下箭頭40;上翻頁:33;下翻頁:34;退出(ESC):27。
不管keydown或者keyup事件都會存在一些特殊狀況。在Firefox和Opera中,按分號鍵時keyCode爲59,也就是ASCII中分號的編碼;但在IE,Safari,Chrome中返回186,即鍵盤中按鍵的鍵碼。
IE九、Firefox、Chrome和Safari的event對象都支持一個charCode屬性,這個屬性只有在發生keypress事件時才包含值,並且這個值是按下的那個鍵所代碼字符的ASCII編碼。IE8及以前的版本和Opera則是在keyCode中保存字符的ASCII編碼。下面以跨瀏覽器取得字符編碼,放在EventUtil對象中:
getCharCode:function(event){ if(typeof event.charCode == 'number'){ return event.charCode; }else{ return event.keyCode; } },
使用方式:
var textbox = document.getElementById('myText'); EventUtil.addHandler(textbox,'keypress',function(event){ event = EventUtil.getEvent(event); console.log(EventUtil.getCharCode(event)); })
在取得字符編碼後,就可使用String.fromCharCode()將其轉化爲實際的字符。以下:
var textbox = document.getElementById('myText'); EventUtil.addHandler(textbox,'keypress',function(event){ event = EventUtil.getEvent(event); var charCode = EventUtil.getCharCode(event); var text = String.fromCharCode(charCode); })
DOM3級事件中的鍵盤事件,再也不包含charCode屬性,而是包含兩個新屬性:key和char,因爲這個兩個屬性各瀏覽器支持程度不同,不推薦使用。
DOM3級事件在添加一個location屬性,也不推薦使用。
「DOM3級事件」規範中引入了一個新事件,textInput。根據規範,當用戶在可編輯區域中輸入字符時,就會觸發這個事件。keypress和textInput的區別:
因爲textInput事件主要考慮的是字符,所以它的event對象中還包含一個data屬性。這個屬性的值就是用戶輸入的字符(而非字符編碼)。
var textbox = document.getElementById('myText'); EventUtil.addHandler(textbox,'textInput',function(event){ event = EventUtil.getEvent(event); console.log(event.data) })
EventUtil.addHandler(window,'load',function(event){ var div = document.getElementById('myDiv'); EventUtil.addHandler(div,'contextmenu',function(event){ event = EventUtil.getEvent(event); EventUtil.preventDefault(event); var menu = document.getElementById('myMenu'); menu.style.left = event.clientX + 'px'; menu.style.top = event.clientY + 'px'; menu.style.visibility = 'visible'; }) EventUtil.addHandler(document,'click',function(event){ document.getElementById('myMenu').style.visibility = 'hidden'; }) })
之因此有發生在window對象上的beforeunload事件,是爲了讓開發人員在頁面卸載前阻止這一操做。能夠經過這個事件來取消卸載並繼續使用原有頁面。
EventUtil.addHandler(window,'beforeunload',function(event){ event = EventUtil.getEvent(event); var message = "你肯定要離開這個頁面嗎?"; event.returnValue = message; return message; })
window的load事件會在頁面中的一切都加載完畢時觸發,但這個過程可能會由於要加載的外部資源過多而頗費周折。而DOMContentLoaded事件則在造成完整的DOM樹以後就會觸發,不理會圖像、js文件、css文件或其它資源是否已經下載完畢。與load事件不一樣,DOMContentLoaded支持在頁面下載的早期添加事件處理程序,這也就意味着用戶可以儘早地與頁面進行交互。
要處理DOMContentLoaded事件,能夠爲document或window添加相應的事件處理程序(儘管這個事件會冒泡到window,但它的目標其實是document)。
EventUtil.addHandler(document,"DOMContentLoaded",function(event){ alert('content loaded'); })
IE9以及其它瀏覽器支持該事件,對於不支持DOMContentLoaded的瀏覽器,建議在頁面加載期間設置一個時間爲0毫秒的超時調用,以下:
setTimeout(function(){ //在此添加事件處理程序 },0)
往返緩存(back-forward cache,或bfcache),用戶使用瀏覽器的「後退」、「前進」按鈕加快頁面的轉換速度。將整個頁面保存在內存裏。
(function(){ var showCount = 0; EventUtil.addHandler(window,'load',function(){ alert('loaded fired') }); EventUtil.addHandler(window,"pageshow",function(event){ showCount++; alert("Show has been fired" + showCount + "times."); }); })()
觸摸事件以下:
上面這幾個事件都會冒泡,也均可以取消。它們是以兼容DOM的方式實現的。所以,每一個觸摸事件的event對象都提供了在鼠標事件中常見的屬性:bubbles 、cancelabel 、view 、clientX 、clientY 、screenX、 screenY 、detail 、altKey、 ctrlKey、 shiftKey 和metaKey。
除了常見的DOM屬性外,觸摸事件還包括下列三個用於跟蹤觸摸的屬性。
每一個touch對象包含下列屬性:
以下面例子:
function handlerTouchEvent(event){ //只跟蹤一次觸摸 if(event.touches.length == 1){ var output = document.getElementById('output'); switch(event.type){ case "touchstart": output.innerHTML = "Touch started(" + event.touches[0].clientX + "," + event.touches[0].clientY + ")"; break; case "touchend": output.innerHTML += "<br/>Touch ended(" + event.changedTouches[0].clientX + "," + event.changedTouches[0].clientY + ")"; break; case "touchmove": event.preventDefault(); //阻止滾動 output.innerHTML += "<br/>Touch moved(" + event.changedTouches[0].clientX + "," + event.changedTouches[0].clientY + ")"; break; } } } EventUtil.addHandler(document,"touchstart",handlerTouchEvent); EventUtil.addHandler(document,"touchend",handlerTouchEvent); EventUtil.addHandler(document,"touchmove",handlerTouchEvent);
注意,在touchend事件發生時,touches集合中就沒有任何的touch對象了,由於不存在活動的觸摸操做;此時就必須轉而使用changedTouches集合。
在觸摸屏幕上的元素時,這些事件(包括鼠標事件)發生的順序以下:
目前只有IOS版的Safari支持多點觸摸。
在JS中,添加到頁面上的事件處理程序數量將直接關係到頁面的總體運行性能。致使這一問題的緣由是多方面的。首先,每一個函數都是對象,都會佔用內存;內存中的對象越多,性能就越差。其次,必須事先指定全部事件處理程序而致使的DOM訪問次數,會延遲整個頁面的交互就緒時間。事實上,從如何利用好事件處理程序的角度出發,仍是有一些辦法可以提高性能的。
事件委託利用事件冒泡,只指定一個事件處理程序,就能夠管理某一類型的全部事件。例如,click事件會冒泡到document層次。也就是說,咱們能夠爲整個頁面添加一個onclick事件處理程序,而沒必要給每一個可單擊的元素分別添加事件處理程序。
以下代碼,點擊3個li分別執行不一樣的操做:
html代碼:
<ul id="myLinks"> <li id="goSomewhere">Go somewhere</li> <li id="doSomething">Do something</li> <li id="sayHi">Say hi</li> </ul>
JS代碼:
var list = document.getElementById('myLinks'); EventUtil.addHandler(list,'click',function(event){ event = EventUtil.getEvent(event); var target = EventUtil.getTarget(event); switch(target.id){ case "goSomewhere": document.title = "I changed the document's title"; break; case "doSomething": window.location.href = "https://www.baidu.com"; break; case "sayHi": alert('hi'); break; } })
html:
<div id="myDiv"> <input type="button" value="click me" id="myBtn"/> </div>
JS:
var btn = document.getElementById('myBtn'); btn.onclick = function(){ //先執行某些操做 btn.onclick = null; document.getElementById('myDiv').innerHTML = 'Processing...'; }
咱們在設置<div>的innerHTML屬性以前,先移除了按鈕的事件處理程序。這樣就確保了內存能夠被再次利用。
注意,在事件處理程序中刪除按鈕也能阻止事件冒泡。目標元素在文檔中是事件冒泡的前提。
能夠在document對象上使用createEvent()方法建立event對象。這個方法接收一個參數,表示要建立的事件類型的字符串。在DOM2級中,全部這些字符串都使用英文複數形式,而在DOM3級中都變成了單數。這個字符串能夠是下列字符串之一:
在建立了event對象後,還須要使用與事件有關的信息對其進行初始化。每種類型的event對象都有一個特殊的方法,爲它傳入適當的數據就能夠初始化該event對象。不一樣類型的方法的名字也不相同,這個取決於createEvent()中使用的參數。
事件模擬的最後一步就是觸發事件,使用dispatchEvent()方法。調用dispatchEvent()方法須要傳入一個參數,即表示要觸發事件的event對象。
爲createEvent()方法傳入「MouseEvents」,返回的對象有一個名爲initMouseEvent()方法,用於指定與該鼠標事件有關的信息。這個方法接收15個參數,分別與鼠標事件中的每一個典型的屬性一一對應,這些參數以下:
默認對按鈕的單擊事件:
var btn = document.getElementById('myBtn'); //建立事件對象 var event = document.createEvent('MouseEvents'); //初始化對象 event.initMouseEvent('click',true,true,document.defaultView,0,0,0,0,0,false,false,false,false,0,null); //觸發事件 btn.dispatchEvent(event);
DOM3級規定,調用createEvent()並傳入「KeyboardEvent」就能夠建立一個鍵盤事件。返回的事件對象包含一個initKeyboardEvent()方法,這個方法接收以下參數:
因爲DOM3級不提倡使用keypress事件,所以只能利用這種技術來模擬keydown和keyup事件。
var textbox = document.getElementById('myTextbox'), event; //以DOM3級方式建立事件對象 if(document.implementation.hasFeature("KeyboardEvents","3.0")){ event = document.createEvent('KeyboardEvent'); //初始化對象 event.initKeyboardEvent('keydown',true,true,document.defaultView,'a',0,'shift',0); } //觸發事件 textbox.dispatchEvent(event);
通用事件模擬:
var textbox = document.getElementById('myTextbox'); var event = document.createEvent('Events'); //初始化對象 event.initEvent('keydown',true,true); event.view = document.defaultView; event.altKey = false; event.ctrlKey = false; event.shiftKey = false; event.metaKey = false; event.keyCode = 65; event.charCode = 65; //觸發事件 textbox.dispatchEvent(event);
當須要模擬變更事件時,可使用createEvent(‘MutationEvents’)建立一個包含initMutationEvent()方法的變更事件對象。這個方法接收的參數包括:type、bubbles、cancelable、relatedNode、preValue、newValue、atrrName和attrChange。以下例子:
var event = document.createEvent('MutationEvents'); //初始化對象 event.initMutationEvent('DOMNodeInserted',true,fase,someNode,"","","",0); //觸發事件 target.dispatchEvent(event);
模擬HTML事件:
var event = document.createEvent('HTMLEvents'); //初始化對象 event.initEvent('focus',true,true); //觸發事件 target.dispatchEvent(event);
DOM3級定義了「自定義事件」。自定義事件不是由DOM原生觸發的,它的目的是讓開發人員建立本身的事件。建立自定義事件,調用createEvent('CustomEvent'),返回的對象有一個名爲initCustomEvent()方法,接收4個參數:
var div = document.getElementById('myDiv'), event; EventUtil.addHandler(div,'myevent',function(event){ console.log("DIV:" + event.detail); //DIV:Hello world! }) EventUtil.addHandler(document,'myevent',function(event){ console.log("DOCUMENT:" + event.detail); //DOCUMENT:Hello world! }) if(document.implementation.hasFeature("CustomEvents","3.0")){ event = document.createEvent('CustomEvent'); event.initCustomEvent("myevent",true,false,"Hello world!"); div.dispatchEvent(event); }
支持自定義DOM事件的瀏覽器有IE9+和Firefox6+。
在IE8及以前的版本中模擬事件跟DOM中的模擬事件的思路相似。只是每一個步驟採用了不同的方式。
調用document.createEventObject()方法能夠在IE中建立event對象,這個方法不接受參數,結果會返回一個通用的event對象。而後,手工給這個對象添加必要的信息。最後一步在目標上調用fireEvent()方法,這個方法接收兩個參數:事件處理程序的名稱和event對象。在調用fireEvent()方法時,會自動爲event對象添加srcElement和type屬性。
下面例子模擬了在一個按鈕上觸發click事件的過程:
var btn = document.getElementById('myBtn'); //建立對象 var event = document.createEventObject(); //初始化事件對象 event.screenX = 100; event.screenY = 0; event.clientX = 0; event.clientY = 0; event.altKey = false; event.ctrlKey = false; event.shiftKey = false; event.button = 0; btn.fireEvent('onclick',event);
注:這裏能夠爲對象添加任意屬性,不會有任何限制。
附上上面完整的EventUtil對象:
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; }, //獲得相關元素信息 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; } }, //取消默認行爲 preventDefault:function(event){ if(event.preventDefault){ event.preventDefault(); }else{ event.returnValue = false; } }, //獲得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; } } }, //取得鼠標滾輪增量值(delta) getWheelDelta:function(event){ if(event.wheelDelta){ return event.wheelDelta; }else{ return -event.detail * 40; } }, //字符編碼 getCharCode:function(event){ if(typeof event.charCode == 'number'){ return event.charCode; }else{ return event.keyCode; } }, //移除事件處理程序 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; } } };