第13章、事件

13.1 事件流

“DOM2級事件”規定事件流包括3個階段:事件捕獲階段,處于目標階段,事件冒泡階段。
事件捕獲表示從最外層節點開始捕獲事件,然后逐級像內傳播直到具體節點。
事件冒泡表示事件由最具體元素開始接受元素,然后逐級向上傳播到文檔節點。


13.2 事件處理程序

13.2.1 DOM2級事件處理程序

“DOM2級事件”為所有DOM節點定義了addEventListener()和removeEventListener()兩個方法,分別用于指定事件處理程序和移除事件處理程序。都接收3個參數:事件名稱、事件處理程序、一個布爾值,布爾值為true,則在捕獲階段調用事件處理程序,布爾值為false,則在冒泡階段調用事件處理程序。
使用DOM2級事件處理程序可以為同一個事件添加多個程序,會按照添加的順序觸發。addEventListener()添加的事件處理程序只能用removeEventListener()來移除,移除時傳入的參數與添加時的相同,無法刪除匿名函數,因為兩個一樣的匿名函數并不是同一個函數。

13.2.2 IE事件處理程序

IE使用attachEvent()和detachEvent(),都接收兩個參數,事件名稱和事件處理程序,只在冒泡階段觸發。事件名稱需要添加“on”前綴,同一事件多個程序會按添加的相反順序觸發。


13.3 事件對象

在DOM上觸發事件時,會產生一個event事件對象。這個對象中包含著所有與事件有關的信息。

13.3.1 DOM中的事件對象

兼容DOM的瀏覽器會將一個event對象傳入事件處理程序中,event對象所有屬性:
bubble,布爾值,只讀,表示事件是否冒泡;
cancelable,布爾值,只讀,表示是否可以取消事件默認行為;
currentTarget,節點,只讀,當前正在處理時間的元素,在事件處理程序內部總是等于this值;
defaultPrevented,布爾值,只讀,為true表示已經調用了preventDefault()方法;
detail,數值信息,只讀,事件相關細節信息;
eventPhase,數值信息,只讀,表示調用事件處理程序的階段,1表示捕獲,2表示“處于目標”,3表示冒泡;
preventDefault(),方法,取消事件的默認行為,cancelable為true時才可以使用;
stopImmediatePropagation(),方法,取消事件進一步冒泡,同時阻止任何事件處理程序被調用;
stopPropagation(),方法,取消事件的進一步冒泡,bubble為true時才可以使用;
target,節點,事件的實際目標;
trusted,布爾值,只讀,為true表示事件是由瀏覽器生成的,為false表示事件由JavaScript添加;
type,字符串,觸發的事件類型;
view,事件發生的window對象。

13.3.2 IE中的事件對象

使用DOM0級添加事件處理程序時,event對象是window的一個屬性,使用attachEvent()時,event可以通過window對象訪問,也會作為參數傳入事件處理程序。IE事件對象屬性:
cancelBubble,布爾值,默認為false,設置為true就可以取消事件冒泡;
returnValue,布爾值,默認為true,設置為false可以取消事件默認行為;
srcElement,節點,事件的實際目標;
type,字符串,事件的名稱。

13.3.3 跨瀏覽器的事件模型
var EventUtil = {
    // 添加事件
    addHandler: function (ele, type, handler) {
        if(ele.addEventListener){
            ele.addEventListener(type,handler,false);
        }else if(ele.attachEvent){
            ele.attachEvent("on"+type,handler);
        }else{
            ele["on"+type] = handler;
        }
    },
    // 移除事件
    removeHandler: function (ele, type, handler) {
        if(ele.removeEventListener){
            ele.removeEventListener(type,handler,false);
        }else if(ele.detachEvent){
            ele.detachEvent("on"+type,handler);
        }else{
            ele["on"+type] = null;
        }
    },
    // 獲取事件對象
    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;
        }
    },
    // 阻止事件冒泡
    stopPropagation:function (event) {
        if(event.stopPropagation){
            event.stopPropagation();
        }else{
            event.cancelBubble = true;
        }
    },
    // 獲取mouseover和mouseout事件觸發時的相關元素
    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;
        }
    },
    // 獲取mousedown和mouseup事件觸發時的按鍵信息
    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;
            }
        }
    },
    // 獲取mousewheel事件(firefox下為DOMMouseScroll事件)鼠標滾輪增量值
    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;
        }
    },
    // 獲取剪貼板中的數據
    getClipboardText:function (event) {
        var clip = (event.clipboardData || window.clipboardData);
        return clip.getData("text");
    },
    // 設置剪貼板中的數據
    setClipboardText:function (event,value) {
        if(event.clipboardData){
            return event.clipboardData.setData("text/plain",value);
        }else if(window.clipboardData){
            return window.clipboardData.setData("text",value);
        }
    }
};

13.4 事件類型

13.4.1 UI事件

load事件,當頁面完全加載后(包括所有圖像,JavaScript文件,CSS文件等外部資源),就會在window上面觸發該事件。
圖像上面也可以觸發load事件,當圖像加載完畢時就會觸發綁定在img圖像上的load事件,在新建圖像元素時(無論是DOM創建img標簽還是創建新的Image對象),只要設置了src屬性,圖像就會開始下載。為保證能觸發事件,要先綁定事件,再為圖像設置src屬性。
<script>元素也會觸發load事件,與圖像不同,只有在設置了<script>元素的src屬性,并且將該元素添加到文檔后,才會開始下載JavaScript文件,所以綁定事件處理程序和設置src屬性的先后順序沒有需求。
unload事件,文檔被完全卸載后觸發,從一個頁面切換到另一個頁面時,就會觸發unload事件。
resize事件,當瀏覽器窗口被調整到一個新的高度或寬度時(包括最大化和最小化),就會觸發resize事件。瀏覽器窗口變化的過程中,resize事件會被不斷的觸發。
scroll事件,當用戶滾動帶滾動條的元素中的內容時,在該元素上面觸發。scroll事件也會在文檔被滾動期間不斷觸發。

13.4.2 焦點事件

focus事件,元素獲得焦點時觸發,不冒泡。
focusin事件,與focus等價,但會冒泡。在focus之后觸發。
blur事件,元素失去焦點時觸發,不冒泡。
focusout事件,與blur等價,但會冒泡。在blur之后觸發。

13.4.3 鼠標與滾輪事件

click事件,單機鼠標左鍵或按下回車時觸發,表示既可以通過鼠標也可以通過鍵盤觸發。
dblclick事件,雙擊鼠標左鍵時觸發。
mousedown事件,按下鼠標任意按鍵時觸發,不能通過鍵盤觸發。
mouseup事件,釋放鼠標任意按鍵時觸發,不能通過鍵盤觸發。
mouseleave事件,在位于元素上方的鼠標光標移動到元素范圍之外時觸發。不冒泡,子元素包含的范圍也是元素的范圍。
mouseenter事件,在鼠標光標從元素外部首次移動到元素范圍之內時觸發。不冒泡,子元素包含的范圍也是元素的范圍。
mouseout事件,鼠標指針從一個元素上方,移入另一個元素時觸發,另一個元素可以是這個元素內部的子元素。
mouseover事件,鼠標指針位于一個元素外部,然后用戶將其首次移入這個元素邊界之內時觸發。
如果mousedown和mouseup中一個被取消,則不會觸發click事件,同樣只有觸發兩次click事件,才會觸發dblclick事件。

clientX和clientY屬性,這兩個屬性保存了鼠標事件發生時,鼠標指針在視口中的水平和垂直坐標。
pageX和pageY屬性,這兩個屬性保存了鼠標事件發生時,鼠標指針在整個頁面中的水平和垂直坐標。頁面沒有滾動時,與clientX和clientY屬性的值相等。
screenX和screenY屬性,這兩個屬性保存了鼠標事件發生時,鼠標指針相對于整個顯示器屏幕的水平和垂直坐標。

shiftkey、ctrlKey、altKey、metaKey4個修改鍵屬性為布爾值,分表表示事件發生時,Shift、Ctrl、ALT、Meta(Window鍵或Cmd鍵)4個按鍵是否被按下。

mouseover和mouseout事件發生時,DOM為事件對象提供了relatedTarget屬性來表示相關元素,mouseover的相關元素是失去光標的那個元素,mouseout的相關元素是獲得光標的元素,其他事件的這個值為null。在IE中,mouseover的相關元素保存在fromElement屬性中,mouseout的相關元素保存在toElement屬性中。

mousedown和mouseup事件發生時,DOM為事件對象提供了button屬性來表示鼠標的按鍵,0表示鼠標主按鍵,1表示鼠標中鍵(鼠標滾輪按鍵),2表示鼠標次按鍵。

事件對象中還有一個detail屬性,表示在指定位置單擊了多少次,相繼發生一次mousedown和一次mouseup算做一次單擊,鼠標移動了位置后detail會被重置為0。

滾輪事件mousewheel,當用戶通過鼠標滾輪與頁面交互時,會觸發mousewheel事件,事件觸發時事件對象上有一個wheelDelta屬性,向上滾動時,這個值是120的倍數,向下滾動時,這個值是-120的倍數。
在Firefox中滾輪事件為DOMMouseScroll,其表示滾輪信息的屬性為detail,向上滾動時,這個值為-3,向下滾動時,這個值為3。

觸摸設備上不支持dblclick事件,雙擊瀏覽器窗口會放大畫面,沒有辦法改變其行為。
兩只手指放在屏幕上且頁面隨手指移動時會觸發mousewheel事件和scroll事件。

13.4.4 鍵盤與文本事件

keydown事件,按下任意按鍵時觸發,按住不放則重復觸發。
keypress事件,按下任意字符按鍵時觸發,按住不放則重復觸發
keyup事件,釋放鍵盤上的按鍵時觸發。
textInput事件,在文本插入文本框之前觸發。
這4個事件的發生順序為keydown,keypress,textInput,keyup,這4個事件也同樣支持修改鍵屬性。

發生keydown和keyup事件時,事件對象keycode中會包含一個鍵碼表示是哪個按鍵,與ASCII碼中的小寫字母或數字的編碼相同。與大小寫無關。唯一不同點為分號,IE/Safari/Chrome返回186,Firefox/Opera返回59。

keypress事件發生時,事件對象有一個charCode屬性,表示所按下按鍵的ASCII編碼,使用String.fromCharCode()方法可以將其轉為實際的字符。

keypress事件和textInput事件的區別:一是所有可以獲得焦點的元素都會觸發keypress事件,但只有可編輯區域會觸發textInput事件,二是只有按下能夠實際輸入字符的按鍵才會觸發textInput事件,但按下影響文本顯示的按鍵也會觸發keypress事件(例如退格按鍵)。

13.4.6 變動事件

DOMSubtreeModified事件,當DOM結構中發生任何變化時觸發,這個事件在其他任何變動事件觸發后都會觸發。
DOMNodeInserted事件,在一個節點作為子節點被插入到另一個節點中時觸發。
DOMNodeRemoved事件,在節點從其父節點中被移除時觸發。
DOMNodeInsertedIntoDocument事件,在一個節點被直接插入文檔,或通過子樹間接插入文檔之后觸發,在DOMNodeInserted之后觸發。
DOMNodeRemoveFromDocument事件,在一個節點被直接從文檔中移除,或通過子樹間接從文檔中移除之后觸發,在DOMNodeRemoved之后觸發。
DOMAttrModified事件,在特性被修改之后觸發。
DOMCharacterDataModified事件,在文本節點的值發生變化時觸發。

在移除節點時,首先會觸發DOMNodeRemoved事件,事件目標為被刪除的節點,而事件對象的relatedNode屬性中包含著目標父節點的引用,與parentNode相同。被刪除的節點和其子節點上,會相繼觸發DOMRemovedFromDocument事件,這個事件不冒泡。緊隨其后的是DOMSubtreeModified事件,這個事件的事件目標是被移除節點的父節點。

在插入節點時,首先會觸發DOMNodeInserted事件,事件目標為被插入的節點,而事件對象的relatedNode屬性中包含著目標父節點的引用,緊接著會觸發DOMNodeInsertedIntoDocument事件,事件目標是被插入的節點,這個事件不冒泡。最后觸發DOMSubtreeModified事件,事件目標是被插入節點的父節點。

13.4.7 HTML5事件

contextmenu事件,鼠標事件的一種,單機鼠標右鍵觸發事件,可以調出瀏覽器的上下文菜單,會冒泡,可以使用event.preventDefalut()取消默認的瀏覽器上下文菜單,從而實現自定義菜單的功能。

beforeunload事件,這個事件會在瀏覽器卸載頁面之前觸發,可以通過它來取消卸載并繼續使用原有頁面。通過設置event.returnValue的值(IE、Firefox)或將需要顯示的信息作為返回值返回(Safari、Chrome)可以在頁面卸載前為用戶顯示對話框提示用戶是否確認卸載。

DOMContentLoaded事件,在形成完整的DOM樹之后就會觸發,不用等待圖片之類的外部資源下載。不支持本事件的瀏覽器,可以設置一個延遲為0的超時調用:

setTimeout(function () { },0);

超時調用為0的意思為:在當前JavaScript處理完成后立即運行這個函數。必須將其作為頁面中的第一個超時調用。

readystatechange事件,提供與文檔或元素的加載狀態有關信息,支持本事件的對象(不是事件對象)都有一個readyState屬性,表示元素的加載狀態,可能包含下列5個值:
1.uninitialized:對象存在但未初始化;
2.loading:對象正在加載數據;
3.loaded:對象加載數據完成;
4.interactive:可以操作對象了,但還沒完全加載;
5.complete:對象加載完畢;
并非所有對象都有全部的5個階段,而且5個階段的順序不一定,檢測操作時,需要同時檢測interactive和complete兩個階段。檢測完成時需要同時檢測loaded和complete兩個階段。并在執行一次操作后就將事件處理程序移除,以防多次執行。

pageshow事件,當頁面是通過前進后退等按鈕從內存中直接加載,會跳過load事件(初次打開頁面時已經執行過load事件),就會觸發pageshow事件,事件目標是document,但事件處理程序必須添加到window對象,初次加載時,pageshow事件在load事件之后觸發。事件對象的persisted屬性保存著一個布爾值屬性,如果頁面被保存在了內存當中,則為true,否則為false。
pagehide事件,該事件會在瀏覽器卸載頁面時觸發,并且在unload事件之前觸發,事件目標是document,但事件處理程序必須添加到window對象,事件對象也包含persisted屬性。
當第一次觸發pageshow時(首次加載頁面),persisted屬性的值一定是false,而在第一次觸發pagehide事件時(首次卸載頁面),persisted的值會變成true。

hashchange事件,URL的參數列表(#號后面的所有字符串)發生變化時觸發,必須把事件處理程序綁定在window對象上,事件對象包含oldURL和newURL兩個屬性,分別保存變化前后的完整URL。最好還是使用location對象來確定當前的參數列表。

13.4.8 設備事件

orientationchange事件,在橫屏和豎屏之間切換會觸發此事件,window.orientation屬性保存狀態信息,0為豎屏正放,90為主屏按鈕在右橫放,-90為主屏按鈕在左橫放。

deviceorientation事件,設備方向發生改變時觸發此事件,觸發時,事件對象包含著每個軸相對于設備靜止狀態下發生的變化信息。
alpha屬性,圍繞Z軸旋轉時,Y軸變化的度數差
beta屬性,圍繞X軸旋轉時,Z軸變化的度數差
gamma屬性,圍繞Y軸旋轉時,Z軸的變化度數差

devicemotion事件,設備發生移動的時候觸發,事件對象包含下列屬性:
acceleration屬性,一個包含x,y,z屬性的對象,在不考慮重力的情況下在每個方向上的加速度。
accelerationIncludingGravity屬性,一個包含x,y,z屬性的對象,在考慮重力的情況下在每個方向上的加速度。
interval屬性,以毫秒表示的時間值。
rotationRate屬性,包含alpha,beta,gamma三個方向屬性的對象

13.4.9 觸摸與手勢事件

touchstart事件,手指觸摸屏幕時觸發,即使已經有一個手指放在屏幕上也會觸發。
touchmove事件,當手指在屏幕上滑動時連續觸發,事件發生時,調用preventDefault()可以阻止滾動。
touchend事件,當手指從屏幕上移開時觸發。

每個觸摸事件的事件對象都包含了鼠標事件中的常見屬性,觸摸事件對象還包含下列3個用于跟蹤觸摸的屬性:
touches屬性,表示當前跟蹤的觸摸操作的Touch對象的數組。
targetTouches屬性,特定于事件目標的Touch對象的數組。
changeTouches屬性,表示自上次觸摸以來發生了什么改變的Touch對象的數組。
在touchend事件發生時,touches集合中就沒有任何Touch對象了,因為不存在活動的觸摸操作,此時必須轉而使用changeTouches集合。

觸摸屏幕上的元素時,事件觸發順序如下:touchstart,mouseover,mousemove,mousedown,mouseup,click,touchend。

手勢事件,當兩個手指觸摸屏幕時就會產生手勢,觸發手勢事件。有3個手勢事件:
gesturestart事件,當一個手指已經按在屏幕上,另一個手指又觸摸屏幕時觸發。
gesturechange事件,當觸摸屏幕的任何一個手指的位置發生變化時觸發。
gestureend事件,當任何一個手指從屏幕上面移開時觸發。

只有當兩只手指都觸摸到事件的接收容器上時才會觸發手勢事件,放上第一只手指時,會觸發touchstart事件,放上第二只手指時,會先觸發gesturestart事件,再觸發第二只手指的touchstart事件,有一個或兩個手指在屏幕上滑動時,將會觸發gesturechange事件,只要有一個手指移開,就會觸發gestureend事件,緊接著又會觸發該手指的touchend事件。

與觸摸事件一樣,手勢事件的事件對象也包含所有常見的屬性,還包含額外的兩個屬性rotation和scale屬性。
rotation屬性表示手指變化引起的旋轉角度,負值表示逆時針旋轉,正值表示順時針旋轉(從0開始),scale屬性表示兩個手指間距離的變化情況,距離拉大增長,距離縮短減小(從1開始)。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容