avaScript与HTML之间的交互是通过事件实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。可以使用侦听器(或处理程序)来预定义事件,以便事件发生时执行相应的代码。这种在传统软件工程中被称为观察者模式的模型,支持页面的行为与页面的外观之间的松散耦合。
事件最早是在IE3和NetscapeNavigator 2中出现的,当时是作为分担服务器运算负载的一种手段。在IE4和Navigator4发布时,这两种浏览器都提供了相似但不相同的API,这些API并存经过了好几个主要版本。DOM2级规范开始尝试以一种符合逻辑的方式来标准化DOM事件。IE9、Firefox、Opera、Safari和Chrome全都实现了"DOM2级事件"模块的核心部分。IE8是最后一个仍然使用其专有事件系统的主要浏览器。
事件流
当浏览器发展到第四代时,浏览器开发团队遇到了一个很有意思的问题:页面的哪一部分会拥有某个特定的事件?要明白这个问题问的是什么,可以想象画在一张纸上的一组同心圆。如果把手指放在圆心上,那么指向的不是一个圆,而是纸上的所有圆。两家公司的浏览器开发团队在看待浏览器事件方面还是一致的。如果单击了某个按钮,都认为单击事件不仅仅发生在按钮上。也就是说,单击按钮的同时,也单击了按钮的容器元素,甚至也单击了整个页面。
事件流描述的是从页面中接收事件的顺序。但有意思的是,IE和Netscape开发团队居然提出了差不多是完全相反的事件流的概念。IE的事件流是事件冒泡流,而Netscape Communicator的事件流是事件捕获流。
事件冒泡
IE的事件流叫做事件冒泡,即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。所有现代浏览器都支持事件冒泡,但在具体实现上还是有一些差别。IE5.5及更早版本中的事件冒泡会跳过<html>元素(从<body>直接跳到document)。IE9、Firefox、Chrome和Safari则将事件一直冒泡到window对象。
事件捕获
NetscapeCommunicator团队提出的另一种事件流叫做事件捕获。事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用以在于在事件到达预定目标之前捕获它。虽然事件捕获是Netscape Communicator唯一支持的事件流模型,但IE9、Safari、Chrome、Opera和Firefox目前也支持这种事件流模型。尽管"DOM2级事件"规范要求事件应该从document对象开始传播,但这些浏览器都是从window对象开始捕获事件的。
DOM事件流
"DOM2级事件"规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。
事件处理程序
事件就是用户或浏览器自身执行的某种动作。诸如click、load和mouseover,嗾使事件的名字。而响应某个事件的函数就叫做事件处理程序(或事件侦听器)。事件处理程序的名字以"on"开头,因此click事件的事件处理程序就是onclick、load事件的事件处理程序就是onload。为事件指定处理程序的方式有好几种。
HTML事件处理程序
某个元素支持的每种事件,都可以使用一个与相应事件处理程序同名的HTML特性来指定。这个特性的值应该是能够执行的JavaScript代码。例如,要在按钮被单击时执行一些JavaScript,可以像下面这样编写代码:
<input type="button"onclick="alert('Clicked')" value="Click!"/>
当单击这个按钮时,就会显示一个警告框。这个操作是通过指定onclick特性并将一些JavaScript代码作为它的值来定义的。由于这个值是JavaScript,因此不能在其中使用未经转义的HTML语法字符,例如和号(&)、双引号("")、小于号(<)或大于号(>)。为了避免使用HTML实体,这里使用了单引号。如果想要使用双引号,那么既要将代码改成如下所示:
<input type="button"onclick="alert("Clicked")" value="Click!"/>
在HTML中定义的事件处理程序可以包含要执行的具体动作,也可以调用在页面其他地方定义的脚本。如下面的例子所示:
<script type="text/javascript">
function showMessage(){
alert("Hello World!");
}
</script>
<input type="button"onclick="showMessage()"value="Click!"/>
在这个例子中,单击按钮就会调用showMessage()函数。这个函数是在一个独立的<script>元素中定义的,当然也可以被包含在一个外部文件中。事件处理程序中的代码在执行时,有权访问全局作用域中的任何代码。
这样指定事件处理程序具有一些独到之处。首先,这样会创建一个封装着元素属性值的函数。这个函数中有一个局部变量event,也就是事件对象。
<!--输出"click" -->
<input type="button"onclick="alert(event.type)" value="Click!">
通过event变量,可以直接访问事件对象,不用自己定义它,也不用从函数的参数列表中读取。在这个函数内部,this值等于事件的目标元素。
<!--输出"Click!" -->
<input type="button"onclick="alert(this.value)" value="Click!">
关于这个动态创建的函数,另一个有意思的地方是它扩展作用域的方式。在这个函数内部,可以像访问局部变量一样访问document及该元素本身的成员。这个函数使用with像下面这样扩展作用域:
function funcName(){
with(document){
with(this){
//元素属性值
}
}
}
如此一来,事件处理程序要访问自己的属性就简单多了。下面这行代码与前面的例子效果相同:
<!--输出"Click!" -->
<input type="button"onclick="alert(value)"value="Click!">
如果当前元素是一个表单输入元素,则作用域中还会包含表单元素(父元素)的入口,这个函数就变成了如下所示:
function funcName(){
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>
在这个例子中,单击按钮会显示文本框中的文本,值得注意的是,这里直接引用了username_元素。不过,在HTML中指定事件处理程序有两个缺点。首先,存在一个时差问题。因为用户可能会在HTML元素一出现在页面上就触发相应的事件,但当时的事件处理程序可能尚不举杯执行条件。以前面的例子来说明,假设showMessage()函数是在按钮下方、页面的最底部定义的。如果用户在页面解析showMessage()函数之前就点击了按钮,就会引发错误。为此,很多HTML事件处理程序就会被封装在try-catch块中,以便错误不会浮出水面。
另一个缺点是,这样扩展时间处理程序的作用域链在不同浏览器中会导致不同结果。不同JavaScript引擎遵循的标识符解析规则略有差异,很可能在访问非限定对象成员时出错。
通过HTML指定时间处理程序的最后一个缺点是HTML与JavaScript代码紧密耦合。如果要更换事件处理程序,就要改动两个地方:HTML代码和JavaScript代码。而这也是许多开发人员摒弃HTML事件处理程序,转而使用JavaScript指定时间处理程序的原因所在。
DOM0 级事件处理程序
通过JavaScript指定事件处理程序的传统方式,就是将一个函数赋值给一个时间处理程序属性。这种为事件处理程序赋值的方法是在第四代Web浏览器中出现的,而且至今仍然为所有现代浏览器所支持。原因一是简单,二是具有跨浏览器的优势。要使用JavaScript指定事件处理程序,首先必须取得一个要操作的对象的引用。
每个元素(包括window和document)都有自己的事件处理程序属性,这些属性通常全部小写,例如onclick。将这种属性的值设置为一个函数,就可以指定事件处理程序,如:
varbtn=document.getElementById("myBtn");
btn.onclick=function(){
alert("Clicked");
}
在此,我们通过文档对象取得了一个按钮的引用,然后为它指定了onclick事件处理程序。但要注意,在这些代码运行以前不会指定事件处理程序,因此如果这些代码在页面中位于按钮后面,就有可能在一段时间内怎么单击都没有反应。
使用DOM 0 级方法指定的事件处理程序被认为是元素的方法。因此,这时候的事件处理程序是在元素的作用域中运行;换句话说,程序中的this引用当前元素。可以在事件处理程序中通过this访问元素的任何属性和方法。以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。
也可删除通过DOM0级方法指定的事件处理程序,只要像下面这样将时间处理程序属性的值设置为null即可:
btn.onclick=null;//删除事件处理程序
如果使用HTML指定事件处理程序,那么onclick属性的值就是一个包含着在同名HTML特性中指定的代码的函数。而将相应的属性设置为null,也可以删除以这种方式指定的事件处理程序。
DOM2级事件处理程序
"DOM2级事件"定义了两个方法,用于处理指定和删除事件处理程序的操作:addEventListener()和removeEventListener()。所有DOM节点中都包含这两个方法,并且它们都接受3个参数:要处理的事件名、作为时间处理程序的函数和一个布尔值。最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。
要在click事件添加事件处理程序,可以使用下列代码:
varbtn=document.getElementById("myBtn");
btn.addEventListener("click",function(){
alert(this.id);
},false);
上面的代码为一个按钮添加了onclick事件处理程序,而且该事件会在冒泡阶段被触发(因为最后一个参数是false)。与DOM0级方法一样,这里添加的事件处理程序也是在其依附的元素的作用域中运行。使用DOM2级方法添加事件处理程序的主要好处是可以添加多个事件处理程序。如:
varbtn=document.getElementById("myBtn");
btn.addEventListener("click",function(){
alert(this.id);
},false);
btn.addEventListener("click",function(){
alert("Hello World");
},false);
这里为按钮添加了两个事件处理程序。这两个事件处理程序会按照添加它们的顺序触发,因此首先会显示元素的ID,其次会显示"Hello World"消息。
通过addEventListener()添加的事件处理程序只能使用removeEventListener()来移除,移除时传入的参数与添加时使用的参数相同。这也意味着通过addEventListener()添加的匿名函数将无法移除,如:
varbtn=document.getElementById("myBtn");
btn.addEventListener("click",function(){
alert(this.id);
},false);
btn.removeEventListener("click",function(){//没有用!
alert(this.id);
},false);
在这个例子中,使用addEventListener()添加了一个事件处理程序。虽然调用removeEventListener()时看似使用了相同的参数,但实际上,第二个参数与传入addEventListener()中的那一个是完全不同的函数。而传入removeEventListener()中的事件处理程序函数必须与传入addEventListener()中的相同,如:
var handler = function(){
alert(this.id);
}
varbtn=document.getElementById("myBtn");
btn.addEventListener("click",handler,false);
btn.removeEventListener("click",handler,false);//有效
重写后的例子没有问题,是因为在addEventListener()和removeEventListener()中使用了相同的函数。
大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段,这样可以最大限度地兼容各种浏览器。最好只在需要在事件达到目标之前截获它的时候将事件处理程序添加到捕获阶段。如果不是特别需要,不建议在事件捕获阶段注册事件处理程序。
IE事件处理程序
IE实现了与DOM中类似的两个方法:attachEvent()和detachEvent()。这两个方法接受相同的两个参数:事件处理程序名称与事件处理程序函数。由于IE8及更早版本只支持事件冒泡,所以通过attachEvent()添加的事件处理程序都会被添加到冒泡阶段。
要使用attachEvent()为按钮添加一个事件处理程序,可以使用以下代码。
varbtn=document.getElementById("myBtn");
btn.attachEvent("onclick",function(){
alert("Clicked");
});
注意,attachEvent()的第一个参数是"onclicke",而非DOM的addEventListener()方法中的"click"。
在IE中使用attachEvent()与使用DOM0级方法的主要区别在于事件处理程序的作用域。在使用DOM0级方法的情况下,事件处理程序会在其所属元素的作用域内运行;在使用attachEvent()方法的情况下,事件处理程序会在全局作用域中运行,因此this等于window。在编写跨浏览器的代码时,牢记这一区别非常重要。
与addEventListener()类似,attachEvent()方法也可以用来为一个元素添加多个事件处理程序。如:
varbtn=document.getElementById("myBtn");
btn.attachEvent("onclick",function(){
alert("Clicked");
});
btn.attachEvent("onclick",function(){
alert("Hello World");
});
这里,调用了两次attachEvent(),为同一个按钮添加了两个不同的事件处理程序。不过,与DOM方法不同的是,这些事件处理程序不是以添加它们的顺序执行,而是以相反的顺序被触发。单击这个例子中的按钮,首先看到的是"Hello World",然后才是"Clicked"。
使用attachEvent()添加的事件可以通过detachEvent()来移除,条件是必须提供相同的参数。
IE11中需要使用addEventListener()和removeEventListener()。
跨浏览器的事件处理程序
为了以跨浏览器的方式处理事件,不少开发人员会使用能够隔离浏览器差异的JavaScript库,还有一些开发人员会自己开发最合适的事件处理的方法。自己编写代码其实也不难,只要恰当地使用能力检测即可。要保证处理事件的代码能在大多数浏览器下一致地运行,只需关注冒泡阶段。
第一个要创建的方法是addHandler(),它的职责是视情况分别使用DOM0级方法、DOM2级方法或IE方法来添加事件。这个方法属于一个叫EventUtil的对象。addHandler()方法接受3个参数:要操作的元素、事件名称和事件处理程序函数。
与addHandler()对应的方法是removeHandler(),它也接受相同的参数。这个方法的职责是移除之前添加的事件处理程序——无论事件程序是采用什么方式添加到元素中的,如果其他方法无效,默认采用DOM0级方法。
var EventUtil={
addHandler:function(element,type,handler){
type=type.toLowerCase();
type=type.substring(0,2)=="on"?type:"on"+type;
if(element.addEventListener){
element.addEventListener(type.substring(2),handler,false);
}else if(element.attachEvent){
element.attachEvent(type,handler);
}else{
element[type]=handler;
}
},
removeHandler:function(element,type,handler){
type=type.toLowerCase();
type=type.substring(0,2)=="on"?type:"on"+type;
if(element.removeEventListener){
element.removeEventListener(type.substring(2),handler,false);
}else if(element.detachEvent){
element.detachEvent(type,handler);
}else{
element[type]=null;
}
}
};
这两个方法首先都会检测传入的元素是否存在DOM2级方法。如果存在DOM2级方法,则使用该方法,其次是IE方法,最后是DOM0级方法。无论传入的事件名称是DOM类型的还是IE中的,都会自动匹配成适合的而且会进行小写转换。使用上面方法时先获取到element,定义好handler事件传入对应的方法中即可。
事件对象
在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息。包括导致事件的元素、事件的类型以及其他与特定事件相关的信息。例如,鼠标操作导致的事件对象中,会包含鼠标位置的信息,而键盘操作导致的事件对象中,会包含与按下的键有关的信息。所有浏览器都支持event对象,但支持方式不同。
DOM中的事件对象
兼容DOM的浏览器会将一个event对象传入到事件处理程序中。无论指定事件处理程序时使用什么方法(DOM0级或DOM2级),都会传入event对象。event对象包含与创建它的特定事件有关的属性和方法。触发的事件类型不一样,可用的属性和方法也不一样。不过,所有事件都会有下表列出的成员。
属性/方法类型读/写说明bubblesBoolean只读表明事件是否冒泡cancelableBoolean只读表名是否可以取消事件的默认行为currentTargetElement只读其事件处理程序当前正在处理事件的那个元素defaultPreventedBoolean只读为true表示已经调用了preventDefault()detailInteger只读与事件相关的细节信息eventPhaseInteger只读调用事件处理程序的阶段:1事件捕获,2处于目标,3冒泡阶段preventDefault()Function只读取消事件的默认行为。如果cancelable为true,可以使用这个方法stopImmediatePropagation()Function只读取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用。stopPropagation()Function只读取消事件的进一步捕获或冒泡。如果bubbles为true,则可以使用这个方法targetElement只读事件的目标trustedBoolean只读为true表示事件是浏览器生成的,false表示事件是由开发人员通过JavaScript创建的typeString只读被触发的事件的类型viewAbstractView只读与事件关联的抽象视图。等同于发生事件的window对象在事件处理程序内部,对象this始终等于currentTarget的值,而target则只包含事件的实际目标。如果直接将事件处理程序指定给了目标元素,则this、currentTarget和target包含相同的值。
在需要通过一个函数处理多个事件时,可以使用type属性。下面使用事件对象做一个例子,模拟购物网站中鼠标在图片上时显示该图片的局部放大图,点击该图片时,在新的网页中显示该图片,需要做一些准备:id为kitty的背景图(beautykitty.jpg)的像素大小为该div的大小,将该图片复制一份(beautykittybig.jpg),并调整大小为原图片宽高x4。然后就可以实验下面的例子了。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Sample Page</title>
</head>
<body>
<table>
<tr>
<td>
<div id='kitty' style=";">
<div id="SQ" style="position: relative;background:#2196F3;;"></div>
</div>
</td>
<td>
<div id='bigImg' style=";"></div>
</td>
</tr>
</table>
</body>
<script type="text/javascript">
var EventUtil={
addHandler:function(element,type,handler){
type=type.toLowerCase();
type=type.substring(0,2)=="on"?type:"on"+type;
if(element.addEventListener){
element.addEventListener(type.substring(2),handler,false);
}else if(element.attachEvent){
element.attachEvent(type,handler);
}else{
element[type]=handler;
}
},
removeHandler:function(element,type,handler){
type=type.toLowerCase();
type=type.substring(0,2)=="on"?type:"on"+type;
if(element.removeEventListener){
element.removeEventListener(type.substring(2),handler,false);
}else if(element.detachEvent){
element.detachEvent(type,handler);
}else{
element[type]=null;
}
}
};
varkitty=document.getElementById('kitty');
var clickHandler=function(){
window.open("http://localhost:8080/demo/fiels/beautykitty.jpg","HellKitty");
}
varmousemoveHandler=function(){
var offsetX=(event.offsetX)*4;
var offsetY=(event.offsetY)*4;
var SQStyle=document.getElementById("SQ").style;
SQStyle.visibility="visible";
SQStyle.top=event.offsetY + "px";
SQStyle.left=event.offsetX + "px";
document.getElementById("bigImg").style.background="url('../fiels/beautykittybig.jpg')-"+offsetX+"px -"+offsetY+"px";
}
varmouseoutHandler=function(){
document.getElementById("bigImg").style.background="gray";
}
EventUtil.addHandler(kitty,"onmousemove",mousemoveHandler);
EventUtil.addHandler(kitty,"onclick",clickHandler);
EventUtil.addHandler(kitty,"onmouseout",mouseoutHandler);
</script>
</html>
做出来的效果就是:
要阻止事件的默认行为,可以使用preventDefault()方法。例如,连接的默认行为就是在被单击时会导航到其href特性指定的URL。如果项阻止连接导航这一默认行为,那么通过连接的onclick事件处理程序就可以取消它,如:
varlink=document.getElementById('myLink');
link.onclick=function(){
if(new Date().getHours() > 15){
event.preventDefault();
}
}
只有cancelable属性设置为true的事件,才可以使用preventDefault()来取消其默认行为。
另外,stopPropagation()方法用于立即停止事件在DOM层次中的传播,即取消进一步的事件捕获或冒泡。例如,直接添加到一个按钮的事件处理程序可以调用stopPropagation(),从而避免触发注册在document.body上面的事件处理程序,如:
varlink=document.getElementById('myLink');
link.onclick=function(){
alert('Link clicked!');
event.preventDefault();
event.stopPropagation();
}
document.body.onclick=function(){
alert("Body clicked!");
}
对于这个例子而言,如果步调用stopPropagation(),就会在单击连接时出现两个警告框。调用stopPropagation()后click事件就不会传播到document.body,因此就不会触发注册在这个元素上的onclick事件处理程序。
事件对象的eventPhase属性,可以用来确定事件当前位于事件流的哪个阶段。如果是在捕获阶段调用的事件处理程序,那么eventPhase等于1;如果事件处理程序处于目标对象上,则eventPhase等于2;如果是在冒泡阶段调用的事件处理程序,eventPhase等于3。这里要注意的是,尽管"处于目标"发生在冒泡阶段,但eventPhase仍然一直等于2。
事件类型
Web浏览器中可能发生的事件有很多类型。如前所述,不同的事件类型具有不同的信息,而"DOM3级事件"规定了以下几类事件。
UI(UserInterface,用户界面)事件,当用户与页面上的元素交互时触发;
焦点事件,当元素获得或失去焦点时触发;
鼠标事件,当用户通过鼠标在页面上执行操作时触发;
滚轮事件,当使用鼠标滚轮(或类似设备)时触发;
文本事件,当在文档中输入文本时触发;
键盘事件,当用户通过键盘在页面上执行操作时触发;
合成事件,当为IME(Input MethodEditor,输入法编辑器)输入字符时触发;
变动事件,当底层DOM结构发生变化时触发;
变动名称事件,当元素或属性名称变动时触发。此类事件已经废弃,没有任何浏览器实现它们。
除了这几类事件之外,HTML5也定义了一组事件,而有些浏览器还会在DOM和BOM中实现其他专有事件。这些专有的事件一般都是根据开发人员需求定制的,没有什么规范,因此不用浏览器的实现有可能也不一致。
DOM3级事件模块在DOM2级事件模块基础上重新定义了这些事件,也添加了一些新事件。包括IE9+在内的所有主流浏览器都支持DOM2级事件。IE9+也支持了DOM3级事件。
UI事件
UI事件指的是那些不一定与用户操作有关的事件。这些事件在DOM规范出现之前,都是以这种或那种形式存在的,而在DOM规范中保留是为了向后兼容。现有的UI事件如下。
DOMActivate:表示元素已经被用户操作(通过鼠标或键盘)激活。这个事件在DOM3级事件中被废弃,但Firefox2+和Chrome支持它。考虑到不同浏览器实现的差异,不建议使用这个事件。
load:当页面完全加载后在window上面触发,当所有框架都加载完毕时在框架集上面触发,当图像加载完毕时在<img>元素上触发,或者当嵌入的内容加载完毕时在<object>元素上面触发。
unload:当页面完全卸载后在window上面触发,当所有框架都卸载后在框架集上面触发,或者当嵌入的内容卸载完毕后在<object>元素上面触发。
abort:在用户停止下载过程时,如果嵌入的内容没有加载完,则在<object>元素上面触发。
error:当发生JavaScript错误时window上面触发,当无法加载如想时在<img>元素上面触发,当无法加载嵌入内容时在<object>元素上面触发,或者当有一或多geese框架无法加载时在框架集上面触发。
select:当用户选择文本框(<input>或<textarea>)中的一或多个字符时触发。
resize:当窗口或框架的大小变化时在window或框架上面触发。
scroll:当用户滚动带滚动条的元素中的内容时,在该元素上面触发。<body>元素中包含加载页面的滚动条。
多数这些事件都与window对象或表单控件相关。
处理DOMActivate之外,其他事件在DOM2级事件中都归为HTML事件(DOMActivate在DOM2级事件中仍然属于UI事件)。要确定浏览器是否支持DOM2级事件规定的HTML事件,可以使用如下代码:
var isSupported =document.implementation.hasFeature("UIEvent","2.0");
注意,只有根据"DOM2级事件"实现这些事件的浏览器才会返回true。而以非标准方式支持这些事件的浏览器则会返回false。要确定浏览器是否支持"DOM3级事件"定义的事件,可以使用如下代码:
var isSupported = document.implementation.hasFeature("UIEvent","3.0");
load事件
JavaScript中最常用的一个事件就是load。当页面完全加载后(包括所有图像、JavaScript文件、CSS文件等外部资源),就会触发window上面的load事件。有两种定义onload事件处理程序的方式。第一种方式是使用如下的JavaScript代码:
EventUtil.addHandler(window,"load", function(event){
alert("Loaded!");
});
这是通过JavaScript来指定事件处理程序的方式,使用的前面定义的跨浏览器的EventUtil对象。与添加其他事件一样,这里也给事件处理程序传入了一个event对象。这个event对象中不包含有关这个事件的任何附加信息,但在兼容DOM的浏览器中,event.target属性的值会被设置为document。
第二种指定onload事件处理程序的方式是为<body>元素添加一个onload特性。一般来说,在window上面发生的任何事件都可以在<body/>元素中通过相应的特性来指定,因为在HTML中无法访问window元素。实际上,这只是为了保证向后兼容的一种权宜之计,但所有浏览器都能很好地支持这种方式,建议尽可能使用JavaScript方式。
图像上面也可以触发load事件,无论是在DOM中的图像元素还是HTML中的图像元素。因此可以在HTML中为任何图像指定onload事件处理程序,例如:
<img src="smile.gif" onload="alert('Imageloaded.')">
这样,当例子中的图像加载完毕后就会显示一个警告框。同样的功能还可以使用JavaScript来实现。
还有一些元素也以非标准的方式支持load事件。在IE9+、Firefox、Opera、Chrome和Safari3+及更高版本中,<script>元素也会触发load事件,以便开发人员确定动态加载的JavaScript文件是否加载完毕。与图像不同,只有在设置了<script>元素的src属性并将该元素添加到文档后,才会开始下载JavaScript文件。换句话说,对于<script>元素而言,指定src属性和指定事件处理程序的先后顺序就不重要了。
unload事件
与load事件对应的是unload事件,这个事件在文档被完全卸载后触发。只要用户从一个页面切换到另一个页面,就会发生unload事件。而利用这个事件最多的情况是清除引用,以避免内存泄漏。
resize事件
当浏览器窗口被调整到一个新的高度或宽度时,就会触发resize事件。这个事件在window(窗口)上面触发,因此可以通过JavaScript或者<body>元素中的onresize特性来指定事件处理程序。
关于何时会触发resize事件,不同的浏览器有不同的机制。IE、Safari、Chrome和Opera会在浏览器窗口变化了1像素时就触发resize事件,然后随着变化不断重复触发,应该注意不要在这个事件的处理程序中加入大计算量的代码,因为这些代码可能被频繁执行,从而导致浏览器反应明显变慢。浏览器窗口最小化或最大化时也会触发resize事件。
scroll事件
虽然scroll事件是在window对象上发生的,但它实际表示的则是页面中相应元素的变化。在混杂模式下,可以通过<body>元素的scrollLeft和scrollTop来监控到这一变化;而在标准模式下,除Safari之外的所有浏览器都会通过<html>元素来反映这一变化(Safari仍然基于<body>跟踪滚动位置)。
与resize事件类似,scroll事件也会在文档被滚动期间重复触发,所以有必要尽量保持事件处理程序的代码简单。
焦点事件
焦点事件会在页面获得或失去焦点时触发。利用这些事件并与document.hasFocus()方法及document.activeElement属性配合,可以知晓用户在页面上的行踪。有以下6个焦点事件。
blur:在元素失去焦点时触发。这个事件不会冒泡;所有浏览器都支持它。
DOMFocusIn:在元素获得焦点时触发。这个事件与HTML事件focus等价,但它冒泡。只有Opera支持这个事件。DOM3级事件废弃了DOMFocusIn,选择了focusin。
DomFocusOut:在元素失去焦点时触发。这个事件是HTML事件blur的通用版本。只有Opera支持这个事件。DOM3级事件废弃了DomFocusOut,选择了focusout。
focus:在元素获得焦点时触发。这个事件不会冒泡;所有浏览器都支持它。
focusin:在元素获得焦点时触发。这个事件与HTML事件focus等价,但它冒泡。
focusout:在元素失去焦点时触发。这个事件是HTML事件blur的通用版本。
这一类事件中最主要的两个是focus和blur,它们都是JavaScript早期就得到所有浏览器支持的事件。这些事件的最发问题是它们不冒泡。因此,IE的focusin和focusout与Opera的DOMFocusIn和DomFocusOut才会发生重叠。IE的方式最后被DOM3级事件采纳为标准方式。
当从页面中的一个元素移动到另一个元素,会依次触发下列事件:
(1) focusout在失去焦点的元素上触发;
(2) focusin在获得焦点的元素上触发;
(3) blur在失去焦点的元素上触发;
(4) DomFocusOut在数去焦点的元素上触发;
(5) focus在获得焦点的元素上触发;
(6) DOMFocusIn在获得焦点的元素上触发。
其中,blur、DomFocusOut和focusout的事件目标是失去焦点的元素;而focus、DOMFocusIn和focusin的事件目标是获得焦点的元素。
要确定浏览器是否支持这些事件,可以使用如下代码:
var isSupported =document.implementation.hasFeature("FocusEvent","3.0");
鼠标与滚轮事件
鼠标事件是Web开发中最常用的一类事件,毕竟鼠标还是最主要的定位设备。DOM3级事件中定义了9个鼠标事件:
click:在用户单击鼠标按钮(一般是左边的按钮)或按下回车键时触发。这一点对确保易访问性很重要,意味着onclick事件处理程序既可以通过键盘也可以通过鼠标执行。
dbclick:在用户双击主鼠标按钮(一般是左边的按钮)时触发。从技术上说,这个事件并不是DOM2级事件规范中规定的,但鉴于它得到了广泛支持,所以DOM3级事件将其纳入了标准。
mousedown:在用户按下了任意鼠标按钮时触发。不能通过键盘触发这个事件。
mouseenter:在鼠标从元素外部首次移动到元素范围之内时触发。这个事件不冒泡,而且在光标移动到后代元素上不会触发。DOM2级事件并没有定义这个事件,但DOM3级事件将它纳入了规范。
mouseleave:在位于元素上方的鼠标光标移动到元素范围之外时触发。这个事件不冒泡,而且在光标移动到后代元素上不会触发。DOM2级事件并没有定义这个事件,但DOM3级事件将它纳入了规范。
mousemove:当鼠标指针在元素内部移动时重复地触发。不能通过键盘触发这个事件。
mouseout:在鼠标指针位于一个元素上方,然后用户将其一如另一个元素时触发。又移入的另一个元素可能位于前一个元素的外部,也可能是这个元素的子元素。不能通过键盘触发这个事件。
mouseover:在鼠标指针位于一个元素外部,然后用户将其首次一如另一个元素边界之内时触发。不能通过键盘触发这个事件。
mouseup:在用户释放鼠标按钮时触发。不能通过键盘触发这个事件。
页面上的所有元素都支持鼠标事件。除了mouseenter和mouseleave,所有鼠标事件都会冒泡,也可以被取消,而取消鼠标事件将会影响浏览器的默认行为。取消鼠标事件的默认行为还会影响其他事件,因为鼠标事件与其他事件是密不可分的关系。
只有在一个元素上相继触发mousedown和mouseup事件,才会触发click事件;如果mousedown和mouseup中的一个被取消,就不会触发click事件。类似地,只有触发两次click事件才会触发依次dbclick事件。如果有代码阻止了连续两次触发click事件(可能是直接取消click事件,也可能通过mousedown或mouseup间接实现),那么就不会触发dbclick事件了。在onclick事件中出现了alert语句时也会阻止触发dbclick事件,如下面的dbclick事件处理程序用于不会得到执行:
<button onclick="alert('Click')" ondblclick="alert('DBClick')">点击</button>
这4个事件触发的顺序始终如下:
(1) mousedown
(2) mouseup
(3) click
(4) mousedown
(5) mouseup
(6) click
(7) dbclick
显然,click和dbclick事件都会依赖与其他事件的触发;然而mousedown和mouseup则不受其他事件的影响。
使用以下代码可以检测浏览器是否支持以上DOM2级事件(除dbclick、mouseenter和mouseleave之外):
var isSupported = document.implementation.hasFeature("MouseEvents","2.0");
要检测浏览器是否支持上面的所有事件,可以使用以下代码:
var isSupported =document.implementation.hasFeature("MouseEvent","3.0");
注意,DOM3级事件的feature名是"MouseEvent",而非"MouseEvents"
客户区坐标位置
鼠标事件都是在浏览器视口的特定位置上发生的。这个位置信息保存在事件对象的clientX和clientY属性中。所有浏览器都支持两个属性,它们的值表示事件发生时鼠标在视口中的水平和垂直坐标。
可以使用类似下列代码取得鼠标事件的客户坐标信息,可在页面中看到随着鼠标的移动,坐标的显示跟随变化:
<body style="background-color:antiqueWhite;height:1000px">
<label id="position"style="">(000,000)</label>
</body>
<script type="text/javascript">
var EventUtil={
addHandler:function(element,type,handler){
type=type.toLowerCase();
type=type.substring(0,2)=="on"?type:"on"+type;
if(element.addEventListener){
element.addEventListener(type.substring(2),handler,false);
}else if(element.attachEvent){
element.attachEvent(type,handler);
}else{
element[type]=handler;
}
},
removeHandler:function(element,type,handler){
type=type.toLowerCase();
type=type.substring(0,2)=="on"?type:"on"+type;
if(element.removeEventListener){
element.removeEventListener(type.substring(2),handler,false);
}else if(element.detachEvent){
element.detachEvent(type,handler);
}else{
element[type]=null;
}
}
};
var label =document.getElementById("position");
var showCoordinate= function(event){
var position="(" + event.clientX + " , " + event.clientY + ")";
label.innerText=position;
label.style.paddingTop=(event.clientY-20)+"px";
label.style.paddingLeft=(event.clientX-50)+"px";
}
EventUtil.addHandler(document,"onmousemove",showCoordinate);
</script>
页面坐标位置
通过客户区坐标能够知道鼠标是在视口中什么位置,而页面坐标通过事件对象的pageX和pageY属性,能够告诉你事件是在页面中什么位置发生的。换句话说,这两个属性表示鼠标光标在页面中的位置,因此坐标是从页面本身而非视口的左边和定边计算的。
在页面没有滚动的情况下,pageX和pageY的值与clientX和clientY的值相等。
IE8及更早版本不支持事件对象上的页面坐标,不过使用客户区坐标和滚动信息可以计算出来。这个时候需要用到document.body(混杂模式)或document.documentElement(标准模式)中的scrollLeft和scrollTop属性。
屏幕坐标位置
鼠标事件发生时,不仅会有相对于浏览器窗口的位置,还有一个相对于整个电脑屏幕的位置。而通过screenX和screenY属性就可以确定鼠标事件发生时鼠标指针相对于整个屏幕的坐标信息。
修改键
虽然鼠标事件主要是使用鼠标来触发的,但在按下鼠标时键盘上某些键的状态也可以影响到索要采取的操作。这些修改键就是Shfit、Ctrl、Alt和Meat(在Windows键盘中是Windows键,在苹果机中是Cmd键),它们经常被用来修改鼠标事件的行为。DOM为此规定了4个属性,表示这些修改键的状态:shfitKey、ctrlKey、altKey和metaKey。这些属性中包含的都是布尔值,如果相应的键被按下了,则值为true,否则为false。当某个鼠标事件发生时,通过检测这几个属性就可以确定用户是否同时按下了其中的键。如:
EventUtil.addHandler(btn,"click",function(event){
var keys=new Array();
if(event.shiftKey){
keys.push("shfit");
}
if(event.ctrlKey){
keys.push("ctrl");
}
if(event.altKey){
keys.push("alt");
}
if(event.metaKey){
keys.push("meta");
}
console.log("keys:" + keys.join(","));
});
相关元素
在发生mouseover和mouseout事件时,还会涉及更多的元素。这两个事件都会涉及把鼠标指针从一个元素的边界之内移动到另一个元素的边界之内。对mouseover事件而言,事件的主要目标是获得光标的元素,而相关元素就是哪个失去光标的元素。类似地,对mouseout事件而言,事件的主要目标是失去光标的元素,而相关元素则是光标的元素。
鼠标按钮
只有在主鼠标按钮被单击(或键盘回车被按下)时才会触发click事件,因此检测按钮的信息并不是必要的。但对于mousedown和mouseup事件来说,则在其event对象存在一个button属性,表示按下或释放的按钮。DOM的button属性可能有如下3个值:0表示主鼠标按钮,1表示中间的鼠标滚轮,2表示次鼠标按钮。在常规的设置中,主鼠标按钮就是鼠标左键,而次鼠标按钮就是鼠标右键。
更多的事件信息
"DOM2级事件"规范在event对象中还提供了detail属性,用于给出有关事件的更多信息。对于鼠标事件来说,detail中包含了一个数值,表示在给定位置上发生了多少次单击。在同一个像素上相继地发生一次mousedown和一次mouseup事件算作一次单击。details属性从1开始计数,每次单击发生后都会递增。如果鼠标在mousedown和mouseup之间移动了位置,则details会被重置为0。
鼠标滚轮事件
IE6.0首先实现了mousewheel事件。此后,Opera、Chrome和Safari也都实现了这个事件。当用户通过鼠标滚轮与页面交互、在垂直方向上滚动页面时(无论是向上还是向下),就会触发mousewheel事件。这个事件可以在任何元素上面触发,最终会冒泡到document(IE8)或window(IE9、Opera、Chrome及Safari)对象。与mousewheel事件对应的event对象除包含鼠标事件的所有标准信息外,还包含一个特殊的wheelDelta属性。当用户向前滚动鼠标滚轮时,wheelDelta是120的倍数;当向后时,是-120的倍数。
键盘与文本事件
用户在使用键盘时会触发键盘事件。"DOM2"级事件最初规定了键盘事件,但在最终定稿之前又删除了相应的内容。结果,对键盘事件的支持主要遵循的是DOM0级。
"DOM3级事件"为键盘事件制定了规范,有3个键盘事件:
keydown:当用户按下键盘上的任意键时触发,而且如果按住不放的话,会重复触发此事件。
keypress:当用户按下键盘上的字符键时触发,而且按住不放的话,会重复触发此事件。按下Esc键也会触发这个事件。
keyup:当用户释放键盘上的键时触发。
虽然所有元素都支持以上3个事件,但只有在用户通过文本框输入文本时才最常用到。
只有一个文本事件:textInput。这个事件是对keypress的补充,用以是在将文本显示给用户之前更容易拦截文本。在文本插入文本框之前会触发textInput事件。
在用户按了一下键盘上的字符键时,首先会触发keydown事件,然后紧跟着是keypress事件,最后会触发keyup事件。其中keydown和keypress都是在文本框发生变化之前被触发的;而keyup事件则是在文本框已经发生变化之后被触发的。如果用户按下了一个字符键不放,就会重复触发keydown和keypress事件,知道用户松开该键为止。
如果用户按下的是一个非字符键,那么首先会触发keydown事件,然后就是keyup事件。如果按住这个非字符键不放,那么就会一直重复触发keydown事件,直到用户松开这个键,此时会触发keyup事件。
键盘事件与鼠标事件一样,都支持相同的修改键。而且,键盘事件的事件对象中也有shfitKey、ctrlKey、altKey和metaKey属性。IE8及低版本不支持metaKey。
键码
在发生keydown和keyup事件时,event对象的keyCode属性中会包含一个代码,与键盘上一个特定的键对应。对数字字母字符键,keyCode属性的值与ASCII码中对应小写字母或数字的编码相同。因此,数字键7的keyCode值为55,而字母A键的keyCode值为65——与Shfit的状态无关。DOM和IE的event对象都支持keyCode属性。无论keydown或keyup事件都会存在一些特殊情况。在Firefox和Opera中,按分号键时keyCode值为59,也就是ASCII分号中的编码;但IE和Safari返回186,即键盘中按键的键码。
字符编码
发生keypress事件意味着按下的键会影响到屏幕中文本的显示。在所有浏览器中,按下能够插入或删除的键都会触发keypress事件;按下其他键能够触发此事件因浏览器而异。
IE9、Firefox、Chrome和Safari的event对象都支持一个charCode属性,这个属性只有在发生keypress事件时才包含值,而且这个值是按下的那个键所代表字符的ASCII编码。此时的keyCode通常等于0或者也有可能所按键的键码。IE8及之前版本和Opera则是在keyCode中保存字符的ASCII编码。要想以跨浏览器的方式取得字符编码,首先必须检测charCode属性是否可用,如果不可用则使用keyCode,如下所示:
var EventUtil={
//……
getCharCode:function(event){
if(typeof event.charCode == "number"){
return event.charCode;
}else{
return event.keyCode;
}
}
//……
};
这个方法首先检测charCode属性是否包含数值(在不支持这个属性的浏览器中,值为undefined),如果是返回该值,否则返回keyCode属性值。在取得了字符编码之后,就可以使用String.fromCharCode()将其转换成实际的字符。
DOM3级变化
尽管所有浏览器都实现了某种形式的键盘事件,DOM3级事件还是做出了一些改变。比如,DOM3级事件中的键盘事件,不再包含charCode属性,而是包含两个新属性:key和char。
其中,key属性是为了取代keyCode而新增的,它的值是一个字符串。在按下某个字符键时,key的值就是相应的文本字符(如"k"或"M");在按下非字符键时,key的值是相应键的名(如"Shift"或"Down")。而char属性在按下字符键时的行为与key相同,但在按下非字符键时值为null。
IE9支持key属性,但不支持char属性。Safari5和Chrome支持名为keyIdentifier的属性,在按下非字符键(例如Shfit)的情况下与key的值相同。对于字符键,keyIdentifier返回一个格式类似"U+0000"的字符串,表示Unicode值。
textInput事件
"DOM3级事件"规范中引入了一个新事件,名叫textInput。根据规范,当用户在可编辑区域中输入字符时,就会触发这个事件。这个用于替代keypress的textInput事件的行为稍微有不同。区别之一就是任何可以获得焦点的元素都可以触发keypress时间爱你,但只有可编辑区才能触发textInput事件。区别之二是textInput事件只会在用户按下能够输入实际字符的键时才会被触发,而keypress事件则在按下哪些能够影响文本显示的键时也会触发(例如退格键)。
由于textInput事件主要考虑的是字符,因此它的event对象中还包含一个data属性,这个属性的值就是用户输入的字符(而非字符编码)。换句话说,用户在没有按上档键的情况下按下了S键,data的值就是"s",而如果在按住上档键时按下该键,data的值就是"S"。
复合事件
复合事件是DOM3级事件中新添加的一类事件,用于处理IME的输入序列。IME(Input Method Editor,输入法编辑器)可以让用户输入在物理键盘上找不到的字符。例如,使用拉丁文键盘的用户通过IME照样能输入日文字符。IME通常需要同时按住多个见,但最终只输入一个字符。复合事件就是针对检测和处理这种输入而设计的。有以下三种复合事件。
compositionstart:在IME的文本复合系统打开时触发,表示要开始输入了。
compositionupdate:在向输入字段中插入新字符时触发。
compositionend:在IME的文本复合系统关闭时触发,表示返回正常键盘输入状态。
复合事件与文本事件在很多方面都很相似。在触发复合事件时,目标是接收文本的输入字段。但它比文本事件的事件对象多一个属性data,其中包含以下几个值中的一个:
如果在compositionstart事件发生时访问,包含正在编辑的文本(例如,已经选中的需要马上替换的文本);
如果在compositionupdate事件发生时访问,包含正插入的新字符;
如果在compositionend事件发生时访问,包含此次输入会话中插入的所有字符。
到2016年,大多数的主流浏览器(Safari除外)都已支持复合事件,要确定浏览器是否支持复合事件,可以使用以下代码:
var isSupported=document.implementation.hasFeature("CompositionEvent","3.0");
变动事件
DOM2级的变动事件能在DOM中的某一部分发生变化时给出提示。变动事件是为XML或HTML DOM设计的,并不特定于某种语言。DOM2级定义了如下变动事件。
DOMSubtreeModified:在DOM结构中发生任何变化时触发。这个事件在其他任何事件触发后都会触发。
DOMNodeInserted:在一个节点作为子节点被插入到另一个节点中时触发。
DOMNodeRemoved:在节点从其父节点中被移除时触发。
DOMNodeInsertedIntoDocument:在一个节点被直接插入文档或通过子树直接插入文档之后触发。这个事件在DOMNodeInserted之后触发。
DOMNodeRemovedFromDocument:在一个节点被直接从文档中移除或通过子树间接从文档中移除之前触发。这个事件在DOMNodeRemoved之后触发。
DOMAttrModified:在特性被修改之后触发。
DOMCharacterDataModified:在文本节点的值发生变化时触发。
使用下列代码可以检测出浏览器是否支持变动事件:
var isSupported=document.implementation.hasFeature("MutationEvents","2.0");
删除节点
在使用removeChild()或replaceChild()从DOM中删除节点时,首先会触发DOMNodeRemoved事件。这个事件的目标(event.target)是被删除的节点,而event.relatedNode属性中包含着对目标节点父节点的引用。在这个事件触发时,节点尚未从其父节点删除,因此其parentNode属性仍然指向父节点(与event.relatedNode相同)。这个事件会冒泡,因而可以在DOM的任何层次上面处理它。
如果被移除的子节点包含子节点,那么在其所有子节点及这个被移除的节点上会相继触发DOMNodeRemovedFromDocument事件。这个事件不会冒泡,所以只有直接指定给其中一个子节点的事件处理程序才会被调用。这个事件的目标是相应的子节点或者那个被移除的节点,除此之外event对象中不包含其他信息。
紧随其后触发的是DOMSubtreeModified事件。这个事件的目标是移除节点的父节点;此时的event对象也不会提供与事件相关的其他信息。
插入节点
在使用appendChild()、replaceChild()或insertBefore()向DOM中插入节点时,相继触发的事件为:DOMNodeInserted(冒泡)、DOMNodeInsertedIntoDocument(不冒泡)、DomSubtreeModified。
HTML5事件
DOM规范中没有涵盖所有浏览器支持的所有事件。很多浏览器出于不同的目的——满足用户需求或解决特殊问题,还实现了一些自定义的事件。HTML5详尽列出了浏览器应该支持的所有事件。
contextmenu事件
表示合适应该显示上下文菜单,以便开发人员取消默认的上下文菜单而提供自定义的菜单。该事件冒泡。在所有浏览器中都可以取消这个事件:在兼容DOM的浏览器中,使用event.preventDefault();在IE中,将event.returnValue的值设置为false。
Beforeunload事件
之所以有发生在window对象上的beforeunload事件,是为了让开发人员有可能在页面卸载前阻止这一操作。这个事件会在浏览器卸载页面之前触发,可以通过它来取消卸载并继续使用原有页面。但是不能彻底取消这个事件,因为那就相当于让用户无法离开当前页面了。为此,这个事件的意图是将控制权交给用户,显示的消息会告知用户页面将被卸载,询问用户是否真的要关闭页面,还是希望继续留下来。类似事件的应用场景很多,比如有些电脑程序退出时会询问是否退出,网页游戏在关闭网页时会询问是否再多玩一会……
DOMContentLoaded事件
如前所述,window的load事件会在页面中的一切都加载完毕时触发,但这个过程可能会因为要加载的外部资源过多而颇费周折。而DOMContentLoaded事件则在形成完整的DOM树之后就会触发,不理会图像、JavaScript文件、CSS文件或其他资源是否已经下载完毕。与load事件不同,DOMContentLoaded支持在页面下载的早起添加事件处理程序,这也就意味着用户能够尽早地与页面进行交互。
要处理DOMContentLoaded事件,可以为document或window添加相应的事件处理程序(尽管这个事件会冒泡到window,但它的实际目标实际上是document)。DOMContentLoaded事件对象不会提供任何额外的信息。IE9+、Firefox、Chrome、Safari3.0+和Opera9+都支持DOMContentLoaded事件,通常这个事件既可以添加事件处理程序,也可以执行其他DOM操作。这个事件始终都会在load事件之前触发。
内存和性能
为页面添加大量的处理程序将降低页面的整体运行性能。导致这一问题的原因是多方面的。首先,每个函数都是对象,都会占用内存;内存中的对象越多,性能就越差。其次,必须事先指定所有事件处理程序而导致的DOM访问次数,会延迟整个页面的交互就绪时间。
事件委托
对"事件处理程序过多"问题的解决方案就是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。例如,click事件会一直冒泡到document层次。也就是说,可以为整个页面指定一个onclick事件处理程序,而不必给每个可单击的元素分别添加事件处理程序。
最适合采用事件委托技术的事件包括click、mousedown、mouseup、keydown、keyup和keypress。虽然mouseover和mouseout事件也冒泡,但要适当处理他们并不容易,而且经常需要计算元素的位置(因为当鼠标从一个元素移到其子节点时,或者当鼠标移出该元素时,都会触发mouseout事件)。
移除事件处理程序
每当将事件处理程序指定给元素时,运行中的浏览器代码与支持页面交互的JavaScript代码之间就会建立一个连接。当事件处理程序不再使用时,应该在适当的时机将其去除。
模拟事件
事件,就是网页中某个特别值得关注的瞬间。事件经常由用户操作或通过其他浏览器功能来触发。但很少有人知道,可以使用JavaScript在任意时刻来触发特定的事件,而此时的事件就如同浏览器创建的事件一样。也就是说,这些事件该冒泡还会冒泡,而且按照能够导致浏览器执行已经指定的处理他们的事件处理程序。在测试Web应用程序,模拟触发事件是一种极其有用的技术。DOM2级规范为此规定了模拟特定事件的方式。IE9,Opera、Firefox、Chrome和Safari都支持这种方式。IE有它自己模拟事件的方式。
DOM中的事件模拟
可以在document对象上使用createEvent()方法创建event对象。这个方法接收一个参数,即表示要创建的事件类型的字符串。在DOM2级中,所有这些字符串都使用英文复数形式,而在DOM3级中都变成了单数。这个字符串可以是下列几个字符串之一:UIEvents、MouseEvents、MutationEvents和HTMLEvents。
创建完成事件对象后,可以调用事件对象的initXXX()方法,为该事件对象指定具体的属性,然后调用HTML对象的dispatchEvent()方法完成事件的模拟,如:
var btn = document.getElementById("myBtn");
var event = document.createEvent("MouseEvents");
event.initMouseEvent("click",true,true,document.defaultView,0,0,0,0,0,false,false,false,false,0,null);
btn.dispatchEvent(event);
我们学习了 HTML 提供的原生拖放(drag & drop)后,是时候想一想这个东西可以用来作什么,可以在什么时候使用,使用的场景等等
场景分析
当我们在注册成功一个账户时,一般网站会让我们上传我们的用户头像,或者在实名认证的时候会涉及到身份证图片上传到等,这时候我们可以使用input提供的file属性进行选择本地文件进行上传。
我们再想一下,当在电脑端的情况下,当用户打开文件选择框时再寻找图片对应的文件夹,再进行选取文件的时候是不是会有点麻烦呢?我们可不可以让用户找到图片文件,直接引入实现上传呢?答案是可以的。
怎么做
经过这些分析后,我们可以尝试使用 HTML5 提供的拖拽,使得目标元素增加读取文件功能,然后使用 ajax 实现图片上传。
谈一谈我们需要使用到的技术:
HTML5 拖拽事件
关于 Drag & Drop 拖拽事件,之前我写过一篇专门介绍的文章,HTML5-拖拽,大家有兴趣的话可以点击链接查看,我在这里就不在多啰嗦了~下面直接出拖拽上传的简要代码示例
var oDragWrap = document.body; //拖进 oDragWrap.addEventListener( "dragenter", function(e) { e.preventDefault(); }, false ); //拖离 oDragWrap.addEventListener( "dragleave", function(e) { dragleaveHandler(e); }, false ); //拖来拖去 , 一定要注意dragover事件一定要清除默认事件 //不然会无法触发后面的drop事件 oDragWrap.addEventListener( "dragover", function(e) { e.preventDefault(); }, false ); //扔 oDragWrap.addEventListener( "drop", function(e) { dropHandler(e); }, false ); var dropHandler = function(e) { //将本地图片拖拽到页面中后要进行的处理都在这 };
获取文件数据 HTML5 File API
File API 中的 FileReader 接口,作为 File API 的一部分,FileReader 专门用来读取文件。我们在这里主要介绍一些 File API 中的 FileList 接口,它主要通过两个途径获取本地文件列表,一是<input type="file"/>的表单形式,另一种则是e.dataTransfer.files拖拽事件传递的文件信息。
var fileList = e.dataTransfer.files;
使用 files 方法将会获取到拖拽文件的数组形式的数据,每个文件占用一个数组的索引,如果索引不存在文件数据,将返回 Null。可以通过length属性获取文件的数量。
var fileNum = fileList.length;
拖拽上传需要注意的是需要判断两个条件
// 检测是否是拖拽文件到页面的操作 if (fileList.length === 0) { return; } // 检测文件是不是图片 if (fileList[0].type.indexOf("image") === -1) { return; }
下面我们看看结合之前的拖拽事件,来实现拖拽图片并在页面中预览
var dropHandler = function(e) { e.preventDefault(); //获取文件列表 var fileList = e.dataTransfer.files; //检测是否是拖拽文件到页面的操作 if (fileList.length == 0) { return; } //检测文件是不是图片 if (fileList[0].type.indexOf("image") === -1) { return; } //实例化file reader对象 var reader = new FileReader(); var img = document.createElement("img"); reader.onload = function(e) { img.src = this.result; oDragWrap.appendChild(img); }; reader.readAsDataURL(fileList[0]); };
当完成以上操作后,相信你可以成功的完成了拖拽图片预览的操作。当你查看 img 标签时会发现,img的src属性是一个超长的文件二进制数据,当你需要很多这种的img元素时,建议将展示区域脱离文档流,让其绝对定位减少页面的 reflow
AJAX 上传图片
既然已经获取到拖拽到web页面中的图片数据了,下一步就是将其发送到服务器端。
总结
内容是《Web前端开发之Javascript视频》的课件,请配合大师哥《Javascript》视频课程学习。
MouseEvent鼠标事件:
DOM2级事件中定义了7个,DOM3级事件增加了2个鼠标事件:
鼠标事件中还有一类滚轮事件,只包括一个mousewheel事件,但此事件已归WheelEvent类了;
document.addEventListener("click", function(event){
console.log(event); // MouseEvent
},false);
document.addEventListener("mousewheel", function(event){
console.log(event); // WheelEvent
},false);
可以检测浏览器是否支持所有事件,如:
var isSupported = document.implementation.hasFeature("MouseEvent", "3.0");
页面上的所有元素都支持鼠标事件;除了mouseenter和mouseleave,其他所有鼠标事件都会冒泡,也可以取消,而取消鼠标事件将会影响浏览器的默认行为,也会影响其他事件;
click事件:
在一个元素上被按下和放开时,click事件就会被触发,包括鼠标单击(通常是左按钮)或键盘回车,或者在脚本中为一个对象执行了click()方法也会触发该事件;
var btn = document.getElementById("btn");
btn.click();
在一个focusable元素上单击时,该元素就获得了焦点,就会触发focus事件和click事件;
function handler(event){
console.log(event.type);
}
var txt = document.getElementById("txt");
txt.addEventListener("click", handler,false);
txt.addEventListener("focus", handler,false);
其触发的顺序为:focus、click;
只有在同一个元素上相继触发mousedown和mouseup事件,才会触发click事件;如果mousedown或mouseup中一个被取消,就不会触发click事件,类似只有触发两次click事件才会触发一次dbclick事件;
这4个鼠标事件触发顺序:
mousedown –> mouseup –> click –> mousedown –> mouseup –> click –> dbclick;
mouseover和mouseout事件:
当鼠标进入或移出元素时触发这两个事件;
示例:鼠标悬停改变表格行的背景色,如:
<style>
#mytable{width: 400px; border-collapse: collapse;}
#mytable td{ height: 20px; border: 1px solid #000;}
</style>
<table id="mytable">
<tr>
<td></td> <td></td> <td></td>
</tr>
<!-- 多行 -->
</table>
<script>
// 表格名称、奇数行背景、偶数行背景、鼠标经过背景、点击后背景
function changeBg(table, oddColor, evenColor,overColor, clickColor){
var rows = document.getElementById(table).rows;
for(var i=0; i < rows.length; i++){
var tr = rows[i];
tr.style.backgroundColor = (tr.sectionRowIndex % 2 == 0) ? oddColor : evenColor;
tr.original = true;
tr.addEventListener("click", function(event){
if(this.original){
this.original = false;
this.style.backgroundColor = clickColor;
}else{
this.original = true;
this.style.backgroundColor = (this.sectionRowIndex % 2 == 0) ? oddColor : evenColor;
}
});
tr.addEventListener("mouseover", function(){
if(this.original)
this.style.backgroundColor = overColor;
});
tr.addEventListener("mouseout", function(){
if(this.original)
this.style.backgroundColor = (this.sectionRowIndex % 2 == 0) ? oddColor : evenColor;
});
}
}
changeBg("mytable", "#FFF", "#ccc", "#cfc", "#f00");
</script>
mouseover和mouseout事件会冒泡,因此,当触发mouseout事件时,有可能鼠标真的离开了目标元素,但也有可能是从这个元素移动到它的子元素上,或者从一个子元素移动到另一个子元素,所以在这种情况下,需要判断鼠标的确切位置;
<div id="mydiv">
<div id="div1">div1</div>
<div id="div2">div2</div>
</div>
<script>
var oDiv = document.getElementById("mydiv");
oDiv.addEventListener("mouseover", function(event){
console.log("mouseover:" + event.target.id);
},false);
oDiv.addEventListener("mouseout", function(event){
console.log("mouseout:" + event.target.id);
},false);
</script>
DOM3提供了两个不冒泡的对应版本mouseenter和mouseleave,如:
oDiv.addEventListener("mouseenter", function(event){
console.log("mouseenter:" + event.target.id);
},false);
oDiv.addEventListener("mouseleave", function(event){
console.log("mouseleave:" + event.target.id);
},false);
示例:图片遮罩,如:
<style>
*{margin:0; padding: 0;}
ul,li{list-style: none;}
ul{display:flex; flex-wrap: wrap;}
li{width: 200px;}
li>a{display: block; width: 100%; position: relative;}
li img{width:200px;}
</style>
<ul id="mylist">
<li><a href="#" title="天下第一山"><img src="images/1.jpg"></a></li>
<li><a href="#" title="zeronetwork"><img src="images/2.jpg"></a></li>
<li><a href="#" title="Javascript"><img src="images/3.jpg"></a></li>
<li><a href="#" title="Web前端开发"><img src="images/4.jpg"></a></li>
</ul>
<script>
window.onload = function(){
var mylist = document.getElementById("mylist");
var aList = mylist.getElementsByTagName("a");
for(var i=0,len=aList.length; i<len; i++){
var a = aList[i];
var mask = null;
a.addEventListener("mouseenter", function(event){
mask = this.getElementsByClassName("mask")[0];
if(!mask){
mask = document.createElement("div");
mask.className = "mask";
}
mask.style.backgroundColor = "rgba(0,0,0,0.8)";
var computedStyle = document.defaultView.getComputedStyle(this, null);
mask.style.width = computedStyle.width;
mask.style.height = computedStyle.height;
mask.style.position = "absolute";
mask.style.color = "#FFF";
mask.style.textAlign = "center";
mask.style.lineHeight = computedStyle.height;
mask.innerHTML = this.title;
this.insertBefore(mask, this.firstChild);
},false);
a.addEventListener("mouseleave", function(event){
var mask = this.getElementsByClassName("mask")[0];
console.log(this);
if(mask){
this.removeChild(mask);
}
},false);
}
}
</script>
mouseleave和mouseenter事件的行为与CSS的:hover 伪类非常相似;
mousemove事件,会频繁触发,所以,在其事件处理程序中不能放置计算密集的任务,或者使用事件节流的方式;
鼠标事件对象:
鼠标事件属于MouseEvent类,该类指的是用户与指针设备( 如鼠标 )交互时发生的事件,其继承自UIEvent类;
MouseEvent类定义了一组专属于鼠标事件的属性,描述了当事件发生时鼠标的位置和按键的状态,也包含了是否有辅助键被按下等所有信息;
客户区坐标位置:
clientX与clientY属性:取得鼠标相对于浏览器视口的坐标位置;
var oDiv = document.getElementById("mydiv");
EventUtil.addHandler(oDiv, "click", function(event){
event = EventUtil.getEvent(event);
console.log(event);
console.log(event.clientX + "," + event.clientY);
});
注,这个位置不包括页面滚动的距离,如果加上窗口的滚动偏移量,就会把鼠标位置转换成文档坐标;
所有浏览器也都实现了x和y属性,其是clientX和clientY的别名;
示例:计算鼠标拖动的直线距离,如:
var obj = {};
function downHandler(event){
obj.x = event.x;
obj.y = event.y;
}
function upHandler(event){
obj.mx = event.x - obj.x;
obj.my = event.y - obj.y;
obj.d = Math.sqrt((Math.pow(obj.mx,2) + Math.pow(obj.my,2)));
console.log(obj.mx, obj.my, obj.d);
}
document.addEventListener("mousedown", downHandler, false);
document.addEventListener("mouseup", upHandler, false);
示例:自定义鼠标样式,如:
document.documentElement.style.cursor = "none";
var cursor = document.createElement("span");
cursor.style.width = "20px";
cursor.style.height = "20px";
cursor.style.position = "absolute";
cursor.style.backgroundColor = "#000";
document.body.appendChild(cursor);
document.addEventListener("mousemove", function(event){
cursor.style.left = event.clientX - cursor.offsetWidth / 2 + "px";
cursor.style.top = event.clientY - cursor.offsetHeight / 2 + "px";
},false);
文档坐标位置:
pageX和pageY属性:取得鼠标光标在文档中的位置;
console.log(event.pageX + "," + event.pageY);
这个位置是包括滚动距离的,在文档没有滚动的情况下,pageX、pageY与clientX、clientY值相等;
IE8及以下不支持文档坐标,不过使用客户区坐标和滚动信息可以计算出来,也就是需要用到scrollLeft和scrollTop属性,如:
var oDiv = document.getElementById("mydiv");
EventUtil.addHandler(oDiv, "click", function(event){
event = EventUtil.getEvent(event);
var pageX = event.pageX,
pageY = event.pageY;
if(pageX == undefined)
pageX = event.clientX + (document.body.scrollLeft || document.documentElement.scrollLeft);
if(pageY == undefined)
pageY = event.clientY + (document.body.scrollTop || document.documentElement.scrollTop);
console.log(pageX + ":" + pageY);
});
屏幕坐标位置:screenX与screenY属性:取得相对于屏幕的位置;
console.log(event.screenX + ":" + event.screenY);
元素坐标位置:
offsetX和offsetY属性,返回与目标元素(target)的内填充边(padding edge)在X和Y轴方向上的偏移量;坐标原点为padding区左上角,因此,如果目标元素有边框,鼠标的位置位于边框上,该属性值为负值;
var oDiv = document.getElementById("mydiv");
oDiv.style.position = "relative";
oDiv.style.left = "100px";
oDiv.style.top = "50px";
document.addEventListener("click", function(event){
console.log("offsetX:" + event.offsetX + ",offsetY:" + event.offsetY);
},false);
如果元素滚动了,也包括offsetLeft和offsetTop值;
示例:绘图:
<style>
canvas{border: 1px solid;}
</style>
<canvas id="mydraw" width="560" height="360"></canvas>
<script>
var isDrawing = false;
var x=0, y=0;
var mydraw = document.getElementById("mydraw");
var context = mydraw.getContext("2d");
mydraw.addEventListener("mousedown", function(event){
x = event.offsetX, y = event.offsetY;
isDrawing = true;
});
mydraw.addEventListener("mousemove", function(event){
if(isDrawing === true){
drawLine(context, x, y, event.offsetX, event.offsetY);
x = event.offsetX, y = event.offsetY;
}
});
window.addEventListener("mouseup", function(event){
if(isDrawing === true){
drawLine(context, x, y, event.offsetX, event.offsetY);
x = 0, y = 0;
isDrawing = false;
}
});
function drawLine(content, x1, y1, x2, y2){
context.beginPath();
context.strokeStyle = "black";
context.lineWidth = 1;
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.stroke();
context.closePath();
}
</script>
movementX和movementY属性:
返回当前事件和上一个mousemove事件之间鼠标在水平或垂直方向上的移动值;
即:currentEvent.movementX = currentEvent.screenX - previousEvent.screenX;
currentEvent.movementY = currentEvent.screenY - previousEvent.screenY;
document.addEventListener("mousemove", function(event){
console.log(event.movementX);
},false);
但IE不支持,并且此属性只有在mousemove事件中才能返回正确的值;
辅助键:
DOM规定了4个属性:shiftkey、ctrlKey、altkey和metaKey,表示当事件发生时shift、ctrl、alt和meta4个键的按下状态,均为布尔值,如果按下为true,反之为false;
var btn = document.getElementById("btn");
EventUtil.addHandler(btn, "click", handler);
function handler(event){
event = EventUtil.getEvent(event);
var keys = new Array();
if(event.shiftKey)
keys.push("shift");
if(event.altKey)
keys.push("alt");
if(event.ctrlKey)
keys.push("ctrl");
if(event.metaKey)
keys.push("meta");
console.log("keys:" + keys.join(","));
}
标准浏览器支持,但IE不支持metaKey属性;
getModifierState(key)方法:返回指定修饰键的当前状态;
参数key可以为Control、Alt、Shift和Meta,注意,大小写是敏感的;
btn.addEventListener("click", function(event){
console.log(event.getModifierState("Control"));
},false);
relatedTarget相关元素:
在发生mouseover和mouseout事件时,会涉及到多个元素;对mouseover而言,事件的主目标是获得光标的元素,而相关元素就是那个失去光标的元素(这个相关元素也可以把它叫做次目标元素);对mouseout事件而言,事件的主目标是失去光标的元素,而相关元素则是获得光标的元素;
DOM通过event对象的relatedTarget属性提供了相关元素的信息,该属性只针于mouseover、mouseout、mouseenter、mouseleave、focusin、focusout、dragenter(拖动元素进入)事件时才有值;对于其他事件,该属性为null;
var oDiv = document.getElementById("mydiv");
oDiv.addEventListener("mouseover", function(event){
console.log(event);
},false);
oDiv.addEventListener("mouseout", function(event){
console.log(event);
},false);
IE8及以下不支持relatedTarget属性,但提供了保存着同样信息不同的属性,在mouseover事件触发时,fromElement属性中保存了相关元素,toElement属性为事件目标;在mouseout事件触发时,toElement属性保存了相关元素,fromElement属性为事件目标;
document.addEventListener("mouseover", function(event){
console.log(event.fromElement);
console.log(event.toElement);
},false);
跨浏览器取得相关元素,添加到eventutil文件中;
getRelatedTarget: function(event){
if(event.relatedTaret)
return event.relatedTaret;
else if(event.toElement)
return event.toElement;
else if(event.fromElement)
return event.fromElement;
else
return null;
}
应用:
var oDiv = document.getElementById("mydiv");
EventUtil.addHandler(oDiv, "mouseout", function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
var relatedTarget = EventUtil.getRelatedTarget(event);
console.log(relatedTarget);
console.log(relatedTarget.tagName);
})
鼠标按钮:
对于mousedown和mouseup事件来说,在其event对象存在一个button属性,表示按下或释放的哪个鼠标按钮;可能有3个值:0主按钮;1中间按钮鼠标滚轮);2次按钮;
btn.addEventListener("mouseup", function(event){
console.log(event.button);
},false);
IE8及以下也提供了button,但其值与DOM的button属性有很大差异:0 没有按下按钮;1按下主按钮;2次按钮;3同时按下主次按钮;4中间按钮;5同时按下主和中间按钮;6次和中间按钮;7同时按下三个;
跨浏览器取得button属性,在eventutil文件中添加:
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;
}
}
}
buttons属性:
当鼠标事件触发的时,如果多个鼠标按钮被按下,将会返回一个或者多个代表鼠标按钮的位掩码:
buttons的值为各键对应值做按位与(+)计算后的值,例如,如果右键(2)和滚轮键(4)被同时按下,buttons的值为 2 + 4 = 6,如:
btn.addEventListener("mousedown", function(event){
console.log(event.button);
console.log(event.buttons);
},false);
属性button和buttons 是不同的,buttons可指示任意鼠标事件中鼠标的按键情况,而 button只能保证在由按下和释放一个或多个按键时触发的事件中获得正确的值;
which属性:
当鼠标事件触发时,表示被按下的按钮,其返回特定按键的数字,0为无、1为左键、2为中间滚轮、3为右键;其是非标准属性,但所有浏览器都支持;
btn.addEventListener("mousedown", function(event){
console.log(event.which);
console.log(event.button);
},false);
注意,此时应该注册mousedown或mouseup事件而不是click事件,因为右击或按下中间滚动不会触发click事件;
detail属性:
DOM2在event对象中提供了detail属性,用于给出有关事件的更多信息;对于鼠标click、mousedown和mouseup事件来说,detail中包含了一个数值,表示目标元素被单击了多少次,其它事件返回0;detail从1开始计数,每次单击都会递增;
console.log(event.detail);
用此属性就可以判断用户是单击、双击还是三击;如果鼠标在mousedown和mouseup之间移动了位置,则detail被重置为0;
IE也通过下列属性为鼠标事件提供了更多信息:
这些属性只有IE支持;
示例:拖动文档元素,当鼠标按下或释放时,会触发mousedown和mouseup事件,因此,通过这两个事件,可以探测和响应鼠标的拖动;如:
function drag(elementToDrag, event){
var scroll = {x:0, y:0};
var startX = event.clientX + scroll.x;
var startY = event.clientY + scroll.y;
var origX = elementToDrag.offsetLeft;
var origY = elementToDrag.offsetTop;
var deltaX = startX - origX;
var deltaY = startY - origY;
if(document.addEventListener){
document.addEventListener("mousemove", moveHandler, true);
document.addEventListener("mouseup", upHandler, true);
}else if(document.attachEvent){
elementToDrag.setCapture();
elementToDrag.attachEvent("onmousemove", moveHandler);
elementToDrag.attachEvent("onmouseup", upHandler);
// 作为mouseup事件看待鼠标捕获的丢失
elementToDrag.attachEvent("onlosecapture", upHandler);
}
// 处理了这个事件,不让任何其他元素看到它
if(event.stopPropagation)
event.stopPropagation();
else
event.cancelBubble = true;
// 现在阻止任何默认操作
if(event.preventDefault)
event.preventDefault();
else
event.returnValue = false;
// 当元素正在被拖动时,这就是捕获mousemove事件的处理程序
// 它用于移动这个元素
function moveHandler(e){
if(!e) e = window.event;
// 移动这个元素到当前鼠标位置
// 通过滚动条的位置和初始单击的偏移量来调整
// var scroll = getScrollOffsets();
var scroll = {x:0,y:0};
elementToDrag.style.left = (e.clientX + scroll.x - deltaX) + "px";
elementToDrag.style.top = (e.clientY + scroll.y - deltaY) + "px";
// 同时不让任何其他元素看到这个事件
if(e.stopPropagation)
e.stopPropagation();
else
e.cancelBubble = true;
}
// 这是捕获在拖动结束时发生的最终mouseup事件的处理程序
function upHandler(e){
if(!e) e = window.event;
// 注销捕获事件处理程序
if(document.removeEventListener){
document.removeEventListener("mouseup", upHandler, true);
document.removeEventListener("mousemove", moveHandler, true);
}else if(document.detachEvent){
elementToDrag.detachEvent("onlosecapture", upHandler);
elementToDrag.detachEvent("onmouseup", upHandler);
elementToDrag.detachEvent("onmousemove", moveHandler);
elementToDrag.releaseCapture();
}
// 并且不让事件进一步传播
if(e.stopPropagation)
e.stopPropagation();
else
e.cancelBubble = true;
}
}
应用:
<div style="position: absolute;left:100px;top:100px; width:150px;background-color: purple;">
<div style="background-color: gray;" onmousedown="drag(this.parentNode, event);">标题栏-拖动我</div>
<p>Lorem ...</p>
</div>
CSS的pointer-events属性:
指定在什么情况下 (如果有) 某个特定的元素可以成为鼠标事件的target;主要用于 SVG元素;
可能的值为:
该属性可以:
<style>
/* 链接不会跳转 */
a[href="https://www.zeronetwork.cn/"]{
pointer-events: none;
}
</style>
<a href="https://www.zeronetwork.cn/">零点网络</a>
<script>
var link = document.querySelector("a");
function handler(event){
console.log(event);
}
// 以下均无效
link.addEventListener("click",handler,false);
link.addEventListener("mouseover",handler,false);
link.addEventListener("drag",handler,false);
</script>
此属性可以通过控制台改变,如在控制台输入:document.querySelector("a").style.pointerEvents = "auto";此时,超链接就可以触发了;
<style>
/* 使所有img对任何鼠标事件(如拖动、悬停、单击等)无反应 */
img{
pointer-events: none;
}
</style>
<img src="images/1.jpg" />
<script>
var img = document.querySelector("img");
function handler(event){
console.log(event);
}
// 以下均无效
img.addEventListener("click",handler,false);
img.addEventListener("mouseover",handler,false);
img.addEventListener("drag",handler,false);
</script>
除了指示该元素不是鼠标事件的目标之外,值none表示鼠标事件“穿透”该元素并且指定该元素“下面”的任何元素;如:
<style>
.container{position: relative; width: 200px; height: 150px;}
.mask{width: 100%; height: 100%; background-color: rgba(0, 0, 0, .5);
position: absolute; pointer-events: none; color:#FFF}
.container img{width: 100%; height: 100%;}
</style>
<div class="container">
<div class="mask"></div>
<a href="https://www.zeronetwork.cn"><img src="images/1.jpg" /></a>
</div>
<script>
var link = document.querySelector(".container>a");
link.addEventListener("mouseover", function(event){
var mask = event.currentTarget.parentNode.querySelector(".mask");
mask.innerHTML = event.currentTarget.title;
},false);
link.addEventListener("mouseout", function(event){
var mask = event.currentTarget.parentNode.querySelector(".mask");
mask.innerHTML = "";
},false);
</script>
示例:取得一个元素的相对鼠标坐标,如:
<style>
.parent{ width:400px; height:400px; padding: 50px; margin:100px; background:#f20; }
.child{ width:200px; height:200px; padding:50px; background:#ff0;}
.child-child{ width:50px; height:50px; background:#00d;}
</style>
<div class="parent" id="parent">
<div class="child">
<div class="child-child"></div>
</div>
</div>
<script>
var parent = document.getElementById("parent");
parent.addEventListener("click",function(event){
console.info(event.offsetX);
});
</script>
使用pointer-events属性后再获取,如为child和child-child类分别添加pointer-events属性,此时打印的值就是相对于parent元素的坐标了;
使用pointer-events来阻止元素成为鼠标事件目标不一定意味着元素上的事件侦听器永远不会触发,如果元素后代明确指定了pointer-events属性并允许其成为鼠标事件的目标,那么指向该元素的任何事件在事件传播过CSS添加pointer-events:none,再为其子元素添加pointer-events:all,此时在子元素上单击就可以触发注册在父元素上的事件处理程序;
当然,位于父元素但不在后代元素上的鼠标活动都不会被父元素和后代元素捕获(鼠标活动将会穿过父元素而指向位于其下面的元素);
var subchild = document.querySelector(".child-child");
subchild.addEventListener("click",function(event){
console.log("child-child");
parent.style.pointerEvents = "auto";
});
该属性也可用来提高滚动时的帧频;例如,当页面滚动时,如果恰巧鼠标悬停在某些元素上,则会触发其上的hover效果或触发onmouseover事件,有可能会造成滚动出现问题,此时,如果对body元素应用pointer-events:none,则会禁用了包括hover在内的鼠标事件,从而提高滚动性能;
<style>
#mydiv:hover{
background-color: lightgreen !important;
}
</style>
<div id="mydiv" style="height: 300px;background-color: purple;"></div>
<div style="height: 1000px;"></div>
<script>
var mydiv = document.getElementById("mydiv");
mydiv.addEventListener("mouseover", function(event){
console.log(event);
},false);
</script>
滚动页面时触发了mouseover事件及hover效果,可以在scroll事件中进行相应的处理,如:
var timeoutId = null;
window.addEventListener("scroll", function(event){
document.body.style.pointerEvents = "none";
if(timeoutId){
clearTimeout(timeoutId);
timeoutId = null;
}else{
timeoutId = setTimeout(function(){
console.log("解禁了");
document.body.style.pointerEvents = "auto";
},500);
}
},false);
部分浏览器不支持该属性,可以判断其支持情况,如:
var supportsPointerEvents = (function(){
var dummy = document.createElement("_");
if(!('pointerEvents' in dummy.style))
return false;
dummy.style.pointerEvents = 'auto';
// 如果是真的属性,则赋值不成功
dummy.style.pointerEvents = 'x';
document.body.appendChild(dummy);
var result = getComputedStyle(dummy).pointerEvents === 'auto';
document.body.removeChild(dummy);
return result;
})();
console.log(supportsPointerEvents);
WheelEvent滚轮事件:
当用户通过鼠标滚轮与页面交互,在垂直方向上滚动页面时(无论向上还是向下),就会触发mousewheel事件;该事件可以在任何元素上触发,最终会冒泡到document或window对象,也可以阻止其默认行为;
document.addEventListener("mousewheel", function(event){
console.log(event); // WheelEvent
},false);
WheelEvent类:
表示用户滚动鼠标滚轮或类似输入设备时触发的事件,用以替代MouseWheelEvent和MouseScrollEvent,mousewheel实际上是属于MouseWheelEvent类,而后面要讲的Firefox中的DOMMouseScroll属于MouseScrollEvent类,它们两者都不属于标准,兼容性也不好,为了统一两者,就出现了标准的WheelEvent类;
WheelEvent类继承自MouseEvent类(MouseEvent类继承自UIEvent类),所有也可以把它看作是鼠标事件,因此,对于WheelEvent事件对象来说,其中也保存着大量与MouseEvent事件同样的属性,例如,四对有关获取坐标的属性、which(值为0)、relatedTarget(为null)等等;还包括辅助键的属性;
mousewheel事件中的event对象,除了保存鼠标事件的所有标准信息外,还包含一个特殊的wheelDelta属性,其指定用户滚动滚轮有多远,当用户向前滚动鼠标滚轮时,该属性值是120的倍数,当向后滚动时,该值是-120的倍数;
EventUtil.addHandler(document, "mousewheel", function(event){
event = EventUtil.getEvent(event);
console.log(event);
console.log(event.wheelDelta);
})
如果要判断用户滚动的方向,只要检测wheelDelta属性的正负号即可;在Opera9.5之前的版本中,wheelDelta的值的正负号是颠倒的;
除了wheelDelta属性外,事件对象还有wheelDeltaX和wheelDeltaY属性,并且wheelDelta和wheelDeltaY的值一直相同;
console.log(event.wheelDelta);
console.log(event.wheelDeltaY);
console.log(event.wheelDeltaX);
IE不支持这两个属性;
Firefox不支持mousewheel事件,但支持一个名为DOMMouseScroll的类似事件,也是在鼠标滚轮滚动时触发,它也被视为鼠标事件,也包含与鼠标事件有关的所有鼠标;而有关鼠标滚轮的信息则保存在detail属性中,该属性与wheelDelta作用相同;当向前滚动鼠标滚轮时,该属性返回-3的位数,反之返回3的位数;
可以把该事件添加到页面中的任何元素,而且该事件会冒泡到window对象;
EventUtil.addHandler(document, "DOMMouseScroll", function(event){
event = EventUtil.getEvent(event);
console.log(event);
console.log(event.detail); // 向前为-3,向后是3
})
detail属性值与wheelDelta的值的关系是:wheelDelta等于detail乘以-40;
可以跨浏览器取得鼠标滚轮增值(delta),并添加到eventutil.js中,如:
getWheelDelta: function(event){
if(event.wheelDelta){
return event.wheelDelta;
}else
return -event.detail * 40;
}
应用时,需要同时注册mousewheel和DOMMouseScroll事件,如:
function handlerMouseWheel(event){
event = EventUtil.getEvent(event);
var delta = EventUtil.getWheelDelta(event);
console.log(delta);
}
EventUtil.addHandler(document, "mousewheel", handlerMouseWheel);
EventUtil.addHandler(document, "DOMMouseScroll", handlerMouseWheel);
另外,DOMMouseEvent事件对象中还有一个axis属性,其返回一个long型常量值,表明鼠标滚轮滚动的方向,当返回常量HORIZONTAL_AXIS,值为1时,表示由鼠标滚轮的横向滚动触发的,当返回VERTICAL_AXIS,值为2时,表示是由鼠标滚轮的纵向滚动触发的;
wheel事件:
DOM3事件定义了一个名为wheel事件,用于替代mousewheel和DOMMouseScroll事件;事件对象中保存着deltaX、deltaY及deltaZ属性:用来获取三个不同的鼠标滚轴;大多数鼠标滚轮是一维或二维的,所以并不能使用deltaZ属性;
EventUtil.addHandler(document, "wheel", function(event){
event = EventUtil.getEvent(event);
console.log(event); // WheelEvent
console.log(event.wheelDelta); // -120
console.log(event.wheelDeltaY); // -120
console.log(event.deltaX); // -0
console.log(event.deltaY); // chrome返回100,Firefox返回63
console.log(event.deltaZ); // 0
});
这些值必须乘以-1.2,才和mousewheel事件的wheelDelta值和正负号相匹配;
wheel事件对象还有一个deltaMode属性,只读,其返回long常量值,表示各delta*的值的单位,其值及所表示的单位如下:
常量值描述
console.log(event.deltaMode);
示例:在Enclose.js文件中定义enclose()函数,可以把一个图片装载到一个容器中,并且能移动这个容器,也能改变容器的大小,如:
function enclose(content, framewidth, frameheight, contentX, contentY){
// 这些参数不仅仅是初始值,它们保存当前状态,能被mousewheel处理程序使用和修改
framewidth = Math.max(framewidth, 50);
frameheight = Math.max(frameheight, 50);
contentX = Math.min(contentX, 0) || 0;
contentY = Math.min(contentY, 0) || 0;
// 创建frame元素,且设置CSS类和样式
var frame = document.createElement("div");
frame.className = "enclose";
frame.style.width = framewidth + "px";
frame.style.height = frameheight + "px";
frame.style.overflow = "hidden"; // 没有滚动条,不能溢出
frame.style.boxSizing = "border-box"; // 能简化调整frame大小的计算
content.parentNode.insertBefore(frame, content);
frame.appendChild(content);
// 确定元素相对于frame的位置
content.style.position = "relative";
content.style.left = contentX + "px";
content.style.top = contentY + "px";
var isFirefox = (navigator.userAgent.indexOf('Gecko') != -1);
// 注册mousewheel事件处理程序
frame.onwheel = wheelHandler;
frame.onmousewheel = wheelHandler;
if(isFirefox)
frame.addEventListener("DOMMouseScroll", wheelHandler, false);
function wheelHandler(event){
var e = event || window.event;
var deltaX = e.deltaX / 3.33 || // wheel事件
e.wheelDeltaX / 4 || // mousewheel事件
0 // 属性未定义
var deltaY = e.deltaY / 3.33 ||
e.wheelDeltaY / 4 ||
(e.wheelDeltaY === undefined && // 如果没有2D属性
e.wheelDelta / 4) || // 就使用1D的滚轮属性
e.detail * -1.2 || // DOMMouseScroll事件
0; // 属性未定义
if(isFirefox && e.type !== "DOMMouseScroll")
frame.removeEventListener("DOMMouseScroll", wheelHandler, false);
// 获取内容元素的当前尺寸
var contentbox = content.getBoundingClientRect();
var contentwidth = contentbox.right - contentbox.left;
var contentheight = contentbox.bottom - contentbox.top;
// 如果按下Alt键,就可以调整frame大小
if(e.altKey){
if(deltaX){
framewidth -= deltaX; // 新宽度,但不能比内容大
framewidth = Math.min(framewidth, contentwidth);
framewidth = Math.max(framewidth, 50); // 不能小于50
frame.style.width = framewidth + "px";
}
if(deltaY){
frameheight -= deltaY;
frameheight = Math.min(frameheight, contentheight);
frameheight = Math.max(frameheight - deltaY, 50);
frame.style.height = frameheight + "px";
}
}else{ // 没有按Alt键,就可以平移frame中的内容
if(deltaX){
var minoffset = Math.min(framewidth - contentwidth, 0);
// 把deltaX添加到contentX中,但不能小于minoffset
contentX = Math.max(contentX + deltaX, minoffset);
contentX = Math.min(contentX, 0);
content.style.left = contentX + "px";
}
if(deltaY){
var minoffset = Math.min(frameheight - contentheight, 0);
contentY = Math.max(contentY + deltaY, minoffset);
contentY = Math.min(contentY, 0);
content.style.top = contentY + "px";
}
}
if(e.preventDefault)
e.preventDefault();
if(e.stopPropagation)
e.stopPropagation();
e.cancelBubble = true;
e.returnValue = false;
return false;
}
}
应用:
<style>
div.enclose{border: 10px solid; margin:10px}
</style>
<img id="content" src="images/1.jpg" />
<script>
window.onload = function(){
enclose(document.getElementById("content"),400,200,-200,-300);
}
</script>
不要混淆wheel事件和scroll事件:wheel事件的默认动作取决于浏览器实现,因此wheel事件不一定会触发scroll事件;即便滚轮事件引发了文档内容的滚动行为,也不表示wheel事件中的delta*值恰好反映文档内容的滚动方向;因此,不要依赖delta*属性获知文档内容的滚动方向,可在文档内容滚动事件中监视target的scrollLeft和scrollTop的变化以推断滚动方向;
*请认真填写需求信息,我们会在24小时内与您取得联系。