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