欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

第13章、事件

程序员文章站 2022-07-12 18:52:29
...

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开始)。