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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容