一、事件流
谈到事件,首要要理解事件流的概念:事件流是指从页面接受事件的顺序;“DOM2级事件”规定事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。目前大部分的浏览器的事件流是事件冒泡,即最开始由具体的元素接收事件,然后逐级传播到不具体的节点,直到传播到windows对象;另一种事件流是事件捕获,目前使用得比较少,是指文档对象先接收到事件,然后逐级向下,一直传播到事件的实际目标。
二、事件处理程序
1、两种表示方式
<input type="button" value = "click me" onclick = "alert(‘Clicked‘)"/>
<input type="button" value = "click me" onclick = "showMessage()"/> <script> function showMessage(){ alert("Click"); } </script>
2、事件处理程序具有event对象,以及扩展了作用域
<!-- 函数中有一个event对象表示事件本身,不用自己定义 --> <input type="button" value = "click me" onclick = "alert(event.type)"/>
//动态创建的函数具有以下的作用域(默认对原有作用域进行了扩展) function(){ with(document){ with(this.form){ with(this){ //元素的属性值 } } }
<form method = "post"> <input type="text" name = "username" value=""/> <!-- 由于扩展了作用域,下列代码的事件可以访问表单的其他元素 --> <input type="button" value = "Echo username" onclick = "alert(username.value)"/> </form>
3、事件处理程序的异常捕获
以上面的例子来说,假设showMessage()函数是在页面的底部定义的,如果用户在解析showMessage()之前就单击了按钮,就会引发错误,因此,很多HTML事件程序会被封装在一个try-catch语句块中:
<input type="button" value = "Echo username" onclick = "try(showMessage();)catch(ex){}"/>
二、DOM0级以及DOM2级的事件处理程序
1、DOM0级
从第四代web浏览器开始,就开始采用“通过将函数赋值给事件处理程序属性”的方式来为事件处理程序赋值。每个函数都有自己的事件处理程序属性,以下是将属性设置成一个函数的例子:
var btn = document.getElementById("myBtn"); btn.onclick = function(){ alert(this.id); }
删除DOM0级的事件处理程序,需要把事件处理程序属性置空。
btn.onclick = null;
2、DOM2级
DOM2级事件定义了两个方法,用于处理和删除事件处理程序的操作,addEventListener()和removeEventListener()。所有的DOM节点都包含这两个操作,并且它们都接受3个方法:要处理的事件名、作为事件处理程序的函数以及一个布尔值;布尔值的true和false分别表示在捕获阶段和在冒泡阶段调用事件处理程序。通过这种方式添加的事件处理程序会按照添加它们的方式触发。
var btn = document.getElementById("myBtn"); var handler = function(){ alert(this.id); } //添加事件处理程序 btn.addEventListener("click",handler,false); //移除事件处理程序 btn.removeEventListener("click",handler,false);
3、IE事件处理程序
IE事件处理程序实现了与DOM相似的两个方法:attachEvent()和detachEvent(),它们的区别是:后者只支持冒泡事件;事件处理程序是在全局作用域运行而不是所属元素的作用域;添加多个事件时不是顺序执行,而是以相反的顺序触发。
var btn = document.getElementById("myBtn"); var handler = function(){ alert(this.id); } //添加事件处理程序 btn.attachEvent("onclick",handler); //移除事件处理程序 btn.detachEvent("onclick",handler);
4、跨浏览器的事件处理程序
要保证事件在大多数情况运行,只需要关注冒泡阶段,并视情况使用DOM0、DOM2级或IE方法来添加事件。
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.attachEvent){ element.detachEvent("on"+type, handler); }else{ element["on"+type] = null; } } };
之后可以像下面这样来使用EventUtil对象:
var btn = document.getElementById("myBtn"); var handler = function(){ alert("clicked"); } EventUtil.addHandler(btn, "click", handler ); EventUtil.removeHandler(btn, "click", handler);
三、事件对象
1、DOM中的事件对象
在触发DOM的某个事件时,会产生一个事件对象event,这个对象包含着所有与事件有关的信息。触发事件的类型不一样,可用的属性和方法也不一样。不过,所有的事件都会有以下成员:
以下是一些常见的使用以上方法和属性的例子:
// 需要通过一个函数处理多个事件时,可以利用type属性 var btn = document.getElementById("myBtn"); var handler = function(event){ switch(event.type){ case "click": alert("Clicked"); break; case "mouseover": event.target.style.backgroundColor = "red"; break; case "mouseout": event.target.style.backgroundColor = ""; break; } }; // 使用DOM0级事件 btn.onclick = handler; btn.onmouseover = handler; btn.onmouseout = handler;
// 阻止事件的默认行为 var link = document.getElementById("myLink"); link.onclick = function(event){ event.preventDefault(); }
// 阻止事件在DOM层次的传播,以下只会出现一个弹窗 var btn = document.getElementById("myBtn"); btn.onclick = function(event){ alert("Clicked"); event.stopPropagation(); } document.body.onclick = function(event){ alert("Body Clicked"); }
2、IE中的事件对象以及跨浏览器的解决方案
IE中的event对象包含的属性与DOM中的不同,具体为:
IE中event对象的全部信息和方法,DOM对象中都有,只是实现方式不同;通过对应关系,可以实现两种事件模型之间的映射。以下通过对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; } }, removeHandler:function(element, type, handler){ if(element.removeEventListener){ element.removeEventListener(type, handler, false); }else if(element.attachEvent){ element.detachEvent("on"+type, handler); }else{ element["on"+type] = null; } }, getEvent:function(event){ // 在ie中,当通过DOM0级方法添加事件处理程序时,event是作为window的一个属性存在的 // 在ie中,当通过DOM2级方法添加事件处理程序时,event可以直接返回 // 在DOM对象中,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{ // IE不支持事件捕获,只能取消冒泡事件 event.cancelBubble = true; } };
四、事件类型
web浏览器中可能发生的事件类型有很多,不同的事件类型具有不同的事件信息;HTML5也定义了一组事件;DOM2级也有事件模块;DOM3级事件模块在DOM2级事件模块的基础上重新定义了事件,也添加了一些新事件;有些浏览器还实现了一些专有事件。以下分别介绍。
1、DOM3级事件
1)UI事件
// load事件,页面完全加载完后,会触发window上的load事件 // 用javascript触发load事件 EventUtil.addHandler(window, "load", function(event){ alert("Loaded"); });
<!--load事件,在html上触发,这是向后兼容的一种权宜之计--> <body onload = "Loaded"> </body>
// 有时候在DOM中添加新元素时,需要确定页面已经加载完毕,可以使用load事件 // 添加新图像 EventUtil.addHandler(window, "load", function(){ var image = document.createElement("img"); EventUtil.addHandler(image, "load", function(event){ event = EventUtil.getEvent(event); alert(EventUtil.getTarget(event).src); }); document.body.appendChild(image); image.src = "smile.gif"; // 新图像元素不一定要从添加到文档后才开始下载,只要指定了src属性,就会开始下载 }); // 添加新脚本 EventUtil.addHandler(window, "load", function(){ var script = document.createElement("script"); EventUtil.addHandler(script, "load", function(event){ alert("loaded"); }); document.body.appendChild(script); script.src = "example.js"; // 新脚本只有在设置了src属性并将其添加到文档后,才会开始下载 }); // 添加新css文件 EventUtil.addHandler(window, "load", function(){ var link = document.createElement("link"); link.type = "text/css"; link.rel = "stylesheet"; EventUtil.addHandler(link, "load", function(event){ alert("css coaded"); }); document.body.appendChild(link); link.href = "example.css"; // 新css文件只有在设置了href属性并将其添加到文档后,才会开始下载 });
2)unload事件
在文档被完全卸载后触发,利用这个事件最多的情况是清除引用,以避免内存泄露。
// 用javascript触发unload事件 EventUtil.addHandler(window, "unload", function(event){ alert("unLoaded"); });
<!--unload事件,在html上触发--> <body onload = "unLoaded"> </body>
3)resize事件
当浏览器调整到新高度或宽度时触发:
EventUtil.addHandler(window, "resize", function(event){ alert("resized"); });
4)scroll事件
页面滚动位置变化时触发,可以通过<body>元素的scrollLeft和scrollTop来监控变化:
EventUtil.addHandler(window, "scroll", function(event){ if(document.compatMode == "CSS1Compat"){ alert(document.documentElement.scrollTop); }else{ alert(body.scrollTop); } });
5)焦点事件
焦点事件一共有6个,当焦点从页面的一个元素移动到另外一个元素时,会依次触发:
- focusout:在失去焦点的元素上触发
- focusin:在获得焦点的元素上触发
- blur:在失去焦点的上元素触发
- DOMFucusOut:在失去焦点的元素上触发
- focus:在获得焦点的元素上触发
- DOMFucusIn:在获得焦点的元素上触发
6)鼠标与滚轮事件
一次双击事件中,相关的事件会按照以下的顺序触发:
- mousedown
- mouseup
- click
- mousedown
- mouseup
- click
- dbclick
以下是获取鼠标坐标位置的一些例子:
// example1: 常用位置获取方式 var div = document.getElementById("myDiv"); EventUtil.addHandler(div, "click", function(event){ event = EventUtil.getEvent(event); // 获取鼠标在浏览器视口的位置 alert("Client coordinates:"+event.clientX + "," +event.clientY); // 获取鼠标在页面的坐标位置 alert("Client coordinates:"+event.pageX + "," +event.pageY); // 获取鼠标在电脑屏幕中的位置 alert("Client coordinates:"+event.screenX + "," +event.screenY); });
// example2:存在页面滚动时,获取鼠标在页面的坐标位置 var div = document.getElementById("myDiv"); EventUtil.addHandler(div, "click", function(event){ event = EventUtil.getEvent(event); var pageX = event.pageX, pageY = event.pageY; if(pageX == undefined){ // 需要用到混杂模式document.body或者标准模式document.documentElement中的scrollLeft和scrollTop属性 pageX = event.clientX + (document.body.scrollLeft || document.documentElement.scrollLeft); } if(pageY == undefined){ pageY = event.clientX + (document.body.scrollTop || document.documentElement.scrollTop); } alert("Client coordinates:"+pageX + "," +pageY); });
虽然鼠标事件主要是使用鼠标来触发的,但是在按下鼠标时,键盘上某些键的状态也可能影响到要采取的操作,这些键是:Shift, Ctrl, Alt, Meta(windows 上是windows键, apple上是cmd键);Dom定义了一些属性来表示这些键的状态,如下例:
// example3: var div = document.getElementById("myDiv"); EventUtil.addHandler(div, "click", function(event){ event = EventUtil.getEvent(event); var keys = new Arrary(); if(event.shiftKey){ keys.push("shift"); } if(event.ctrlKey){ keys.push("ctrl"); } if(event.altKey){ keys.push("alt"); } if(event.metaKey){ keys.push("meta"); } alert("keys:" + keys.join(",")); });
鼠标滚轮事件:当用户通过鼠标与页面交互、在垂直方向上滚动页面时,就会触发鼠标滚轮事件mousewheel(IE, Chrome, Opera, safari)或者DOMMouseScroll(firefox),这个事件可以在任意元素触发,最终冒泡到document或者window。
另外,触摸设备是没有鼠标的,这些设备相关的事件,具有以下特点:
- 不支持dbclick事件
- 轻击可单击元素会触发mousemove事件
- mousemove事件也会触发mouseover和mouseout事件
- 两个手指放在触摸屏上且页面随手指滚动时会触发mousewheel和scroll事件
7)键盘与文本事件
对键盘事件的支持主要是DOM0级,DOM2级没有对此作出规定,DOM3级对此实现了规范。键盘事件有3个,文本事件有1个:
- keydown:用户按下任意键时触发
- keypress:用户按下键盘上的字符键触发
- keyup:用户释放键盘的键时触发
- textInput:在文本插入文本框之前触发,用意是在将文本显示给用户之前拦截文本进行处理
在发生keydown和keypress事件时,event对象的keycode或者charcode属性会包含一个代码,与键盘上一个特定的键对应。由于不同浏览器对该属性的支持不同,要以跨浏览器的方式取得字符编码,必须首先检测charcode属性是否可用;如不可用,再使用keycode, 如下例:
function getCharCode(event){ if(typeof event.charCode == "number"){ return event.charCode; }else{ return event.keyCode; } }
DOM3级事件新增了key, keyIdentifier和char属性来实现键盘事件,但是由于存在跨浏览器的问题,因此不推荐使用。另外,DOM3级事件还添加了一个名为location的属性,表示按下了什么位置的键,1表示左侧位置(例如左侧位置的alt键),2表示右侧位置(例如右侧位置的shift键)。支持location属性的浏览器也不多,因此也不推荐使用。
8)复合事件
复合事件是DOM3新添加的一类事件,用于处理IME的输入序列。IME(input method editor, 输入法编辑器),可以让用户在物理键盘输入找不到的字符。有以下三种复合事件:
- compositionstart: 在IME的文本复合系统打开时触发,表示要开始输入了。
- compositionupdate: 在向输入字段中插入新字符时触发。
- compositionend: 在IME的文本复合系统关闭时触发,表示返回正常键盘输入状态。
对于跨浏览器的开发,复合事件的用处也不大。
9)变动事件
DOM2级的变动事件能在DOM的某一部分发生变化时给出提示;定义了如下变动事件:
- DOMSubtreeModified:在DOM结构发生任何变化时触发
- DOMNodeInserted:在一个节点作为子节点被插入到另一个节点时触发
- DOMNodeRemoved:在节点从其父节点中被移除时触发
- DOMInsertedIntoDocument:在一个节点被直接插入文档或通过子树间接插入文档后触发
- DOMNodeRemovedFromDocument:在一个节点直接从文档中移除或间接从文档中移除后触发
- DOMAttrModified:在特性被修改之后触发
- DOMCharacterDataModified:在文本节点的值发生变化时触发
2、HTML5事件
DOM规范没有涵盖所有浏览器支持的事件,很多浏览器出于不同的目的,实现了一些自定义事件。HTML5详尽的列出了浏览器应该支持的事件,下面简单介绍:
1)contextmenu事件
Windows95在PC上引入了上下文菜单的概念,即通过鼠标右键单击可以调出上下文菜单。开发人员面临的问题是,如何确定应该显示上下文菜单,以及如何屏蔽与该操作关联的默认上下文菜单。contextmenu事件是为了解决这个问题而提出的。
2)beforeunload事件
这个事件会在浏览器卸载页面之前触发,这个事件的意图是将页面的控制权交给用户。例如,通过弹窗告知用户页面将进行卸载,询问用户是否真的要关闭页面,还是希望继续留下来。
3)DOMContentLoaded事件
window的load事件会在页面中的一切都加载完毕后触发,但这个过程可能会因为要加载的外部资源过多而颇费周折。而DOMContentLoaded事件则在形成完整的DOM树后就会触发,丝毫不理会图像、JAVASCRIPT文件、CSS文件或者其他资源是否下载完毕。
4)readystatechange事件
这个事件的目的是提供与文档或元素的加载状态有关的信息,支持该事件的每个对象都有一个readyState属性,可能包含下列5各值中的一个:
- uninitialized: 对象存在但尚未初始化
- loading:对象正在加载数据
- loaded:对象加载数据完成
- interactive:可以操作对象了,但还没有完全加载
- complete:对象已经加载完毕
3、设备事件
智能手机与平板电脑的普及,为用户与浏览器的交流引入了一种新的方式,一种新一类的事件——设备事件也应运而生。下面简要介绍几种设备事件:
1)orientationchange事件
苹果公司为移动safari中添加了orientationchange事件,以便开发人员能够确定用户何时将设备由横向查看模式切换为纵向查看模式。
2)MozOrientation事件
Firefox为检测设备的方向引入了MozOrientation事件。当设备的加速计检测到设备方向改变时,就会触发这个事件。
3)deviceorientation事件
该事件与MozOrientation事件相似,也是加速计检测到设备方向变化时,在window对象上触发,不过deviceorientation事件的意图是告诉开发人员,设置在空间中朝向哪儿,而不是如何移动。
4)devicemotion事件
该事件是要告诉开发人员设备什么时候移动,而不仅仅是设备方向如何移动。例如,通过devicemotion事件可以检测到设备是不是正在往下掉,或者是不是被走着的人拿在手里。
4、触摸与手势事件
随着触摸设备的增加,一些少数设备的专有事件也开始变成了事实标准。Touch Events定义了如下触摸事件和手势事件:
1)触摸事件
- touchstart:手指触摸屏幕时触发
- touchmove:手指在屏幕上滑动时连续触发
- touchend:手指在屏幕上移开时触发
- touchcancel:当系统停止跟踪触摸时触发
2)手势事件
- gesturestart:当一个手指已经按在屏幕上而另一个手指又触摸屏幕时触发
- gesturechage:当触摸屏幕的任何一个手指的位置发生变化时触发
- gestureend:当任何一个手指从屏幕上移开时触发