事件
事件类型
- 由于事件类型这一小节篇幅很长,所以我决定再细化一下。
- 以下是DOM3级事件规定的几类事件:
- UI(User Interface,用户界面)事件,当用户与页面上的元素交互时触发。
- 焦点事件,当元素获得或失去焦点时触发。
- 鼠标事件,当用户通过鼠标在页面上执行操作时触发。
- 滚轮事件,当使用鼠标滚轮(或类似设备)时触发。
- 文本事件,当在文档中输入文本时触发。
- 键盘事件,当用户通过键盘在页面上执行操作时触发。
- 合成事件,当为IME(Input Method Editor,输入法编辑器)输入字符时触发。
- 变动(mutation)事件,当底层DOM结构发生变化时触发。
- 变动名称事件,当元素或属性名变动时触发(已废弃)。
- 除了这几类事件之外,HTML5也定义了一组事件。而有些浏览器还会在DOM和BOM中实现其他专有事件。这些专有事件一般没有什么规范,不同的浏览器实现方式也有所不同。
UI事件
- UI 事件指的是那些不一定与用户操作有关的事件。来看看UI事件:
- DOMActivate:表示元素已经被用户操作(通过鼠标或键盘)激活。这个事件在DOM3 级事件中被废弃,但Firefox 2+和Chrome 支持它。考虑到不同浏览器实现的差异,不建议使用这个事件。
- load:当页面完全加载后在window上面触发,当所有框架都加载完毕时在框架集上面触发,当图像加载完毕时在
<img>
元素上面触发,或者当嵌入的内容加载完毕时在<object>
元素上面触发。 - unload:当页面完全卸载后在window上面触发,当所有框架都卸载后在框架集上面触发,或者当嵌入的内容卸载完毕后在
<object>
元素上面触发。 - abort:在用户停止下载过程时,如果嵌入的内容没有加载完,则在
<object>
元素上面触发。 - error:当发生JavaScript 错误时在window上面触发,当无法加载图像时在
<img>
元素上面触发,当无法加载嵌入内容时在<object>
元素上面触发,或者当有一或多个框架无法加载时在框架集上面触发。第17章将继续讨论这个事件(现在我也不知道具体功能)。 - select:当用户选择文本框(
<input>
或<texterea>
)中的一或多个字符时触发。第14章将继续讨论这个事件。 - resize:当窗口或框架的大小变化时在window或框架上面触发。
- scroll:当用户滚动带滚动条的元素中的内容时,在该元素上面触发。
<body>
元素中包含所加载页面的滚动条。
- 除了DOMActivate之外,其他事件在DOM2 级事件中都归为HTML 事件(DOMActivate 在DOM2级中仍然属于UI 事件)。要确定浏览器是否支持DOM2 级事件规定的HTML 事件,可以使用如下代码:
var isSupported = document.implementation.hasFeature("HTMLEvents", "2.0")
- 要确定浏览器是否支持“DOM3 级事件”定义的事件,可以使用如下
代码:
var isSupported = document.implementation.hasFeature("UIEvent", "3.0")
- (为了方便,我下列使用的代码都是基于支持DOM2级事件的浏览器,除非特别说明,否则IE8-(IE9+两种方式都可以)等浏览器则请用其特有的添加事件的方式。)
load事件
- 这个事件非常常见。当页面完全加载后(包括所有图形、JavaScript文件、CSS文件等外部资源),就会触发window上面的load事件。所以我们可以用下面的代码为window添加load事件处理程序:
function loaded(event) {
alert("Loaded!");
alert(event.target === document);
alert(event.target === document.body);
alert(event.target === window);
}
window.addEventListener("load", loaded, false);
window.onload = loaded;
- 这里要注意的是,虽然是为window绑定事件,但event对象中的target会被设置为document(IE9+中target不会改变)。而在IE浏览器中,当使用attachEvent()方法设置load事件,event.srcElement不会被设置(包括IE9+)。
function loaded(event) {
alert("Loaded!");
alert(event.srcElement);
}
window.attachEvent("onload", loaded);
- 至于为什么会被设置为document是有原因的:根据DOM 2级事件规范,应该在document上而非window上触发load事件,但是所有浏览器都在window上面实现了该事件,以确保向后兼容。所以该事件从原理上应该是document上触发的,于是target就被设置为了document。
- 第二种设置load事件的方式就是为
<body>
元素添加onload特性。这个和设置onclick是一样的,不过需要记住是设置在body上而不是html上。
<!DOCTYPE html>
<html>
<head>
<title>Load Event Example</title>
</head>
<body onload="alert('Loaded!');alert(event.target);">
<p>Load event example.</p>
</body>
</html>
----------------------
即使是在onload特性中指定事件处理程序,target依旧指向document。而在IE中依旧是null。
- 因为在HTML中无法访问window元素,所以在window上面发生的任何事件都可以在
<body>
元素中通过相应的特性来指定,这不过是一种保证向后兼容的权宜之计,但所有浏览器都很好地支持这个方式。建议还是使用JS指定事件处理程序的方式。 - img元素同样也有load事件:
<img src="smile.gif" onload="alert('Image loaded.')" />
- 当然也可以用JS的方式去指定img元素的事件处理程序,这里就不多说了。
- 有个有意思的地方需要注意,当我们要在文档中新建一个img节点,并且设置load事件时,我们需要先设置它的onload特性(或者用addEventListener()),再去设置src特性的值。这是因为img元素在设置src特性后会立即开始下载。所以为了避免下载完毕后还未执行到设置load事件的代码,我们一般先设置load事件。
window.addEventListener("load", function(){
var image = document.createElement("img");
image.src = "smile.gif";
image.addEventListener("load", function(event){
alert(event.target.src);
});
document.body.appendChild(image);
});
- 还有一些元素也以非标准的方式支持load事件。在IE9+、Firefox、Safari3+、Opera、Chrome中,
<script>
也会触发load事件,以便开发人员确定动态加载的JavaScript文件是否加载完毕。与图像不同,只有在设置了src属性并将元素添加到文档后,才会开始下载JavaScript文件。某些浏览器<link>
也会触发load事件,同样也是在元素加入文档后才会开始下载CSS文件。
<script type="text/javascript" src="EventUtil.js" onload="alert('Loaded');"></script>
<link href="example.css" type="text/css" rel="stylesheet" onload="alert('css loaded')">
unload事件
- 顾名思义,这个事件的除非条件与load刚好相反。如果是针对当前页面卸载的事件,可以直接为window指定onunload事件处理程序,也可以为body添加onunload特性(与load事件设置方式相同)。可以用刷新页面的方式去触发这个事件。
var 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;
}
}
addHandler(window, "unload", function(event){
alert("Unloaded");
});
- 上面的代码在Chrome中是没有办法弹窗的。我找了一下原因,貌似是Chrome浏览器的设定问题,一种说法是弹窗被认为是广告就被屏蔽了,还有一种说法是在页面关闭期间,弹窗这种行为的方法已经被销毁了不能再调用。而IE9中刷新页面居然不调用console.log()(也许我说的不对,但是辣鸡IE浏览器我实在不会调试,也懒得去学,打印连对象结构都看不到,真是辣鸡)。
- 要注意的是unload事件是一切被卸载之后才触发,也就是说此时document已经被销毁了,如果你的事件处理程序还要去访问document,那必然是会出错的。无独有偶,DOM2级事件规定应该在body元素而非window上触发unload事件。不过所有浏览器为了确保向后兼容,都在window上触发unload事件(我做了个测试:在Chrome上target指向document,在IE9+上srcElement指向window,IE8-上没有变化)。
- 与unload事件类似的还有一个beforeunload事件。这个事件触发后会弹出一个询问框(问你是否要离开这个页面)。这个事件在我看来和unload事件的区别应该是这个事件是在一切被卸载之前触发。所以该事件的事件处理程序应该可以访问document。
<p id="xx">onbeforeunload</p>
window.onbeforeunload = function(){
console.log(document.getElementById("xx").innerHTML);
return "真的离开?";//该方法必须返回一个值作为提示信息,在IE中会显示在提问框中。Chrome中则不知道去哪了。
//不返回值,或返回空类型的值(undefined null)在Chrome中不会触发弹窗提示。(null 在IE中会显示)
//注意在做这个实验时,IE必须调出调试界面,不然可能不会弹窗。
}
resize事件
- 当浏览器窗口被调整到一个新的高度或宽度,就会触发resize事件。与load和unload类似。该事件在window上触发,可以通过JS或者body元素中的onresize特性来指定事件处理程序。传入的event对象的target为document。IE8-则未提供任何属性。不同浏览器有不一样的触发机制。书上说主流浏览器中除了Firefox(用户停止调整窗口时才触发)以外,其他浏览器都是在改变了1像素的情况下就会触发。所以千万不要在这个事件处理程序中加入计算量大的代码。因为我这里没有Firefox,我只对IE和Chrome做了简单测试:
window.onresize = function(){
console.log(window.innerWidth + " " + window.innerHeight)
}
//document.documentElement.clientWidth
//document.documentElement.clientHeight IE8-
- 与resize事件类似,scroll事件也会在文档被滚动期间重复触发。直接上代码:
<!DOCTYPE html>
<html>
<head>
<title>Scroll Event Example</title>
</head>
<body>
<p style="height: 10000px">Scroll event example - scroll the browser window.</p>
<script type="text/javascript">
var 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;
}
}
addHandler(window, "scroll", function(event){
if (document.compatMode == "CSS1Compat"){
console.log("1:" + document.documentElement.scrollTop);
} else {
console.log("2:" + document.body.scrollTop)
}
});
</script>
</body>
</html>
焦点事件
- 焦点事件会在页面获得或失去焦点时触发。利用这些事件和document.hasFocus()方法及document.activeElement属性配合,可以知晓用户在页面上的行踪。以下是6个焦点事件:
- blur:在元素失去焦点时触发。这个事件不会冒泡;所有浏览器都支持它。
- DOMFocusIn:在元素获得焦点时触发。这个事件与HTML事件focus等价,但它冒泡。只有Opera支持这个事件(但如今Chrome也支持了,其他浏览器可能也支持)。DOM3级事件废弃了这个事件,选择了focusin。
- DOMFocusOut:在元素失去焦点是触发。这个事件是HTML事件blur的通用版本。只有Opera支持这个事件(但如今Chrome也支持了,其他浏览器可能也支持)。DOM3级事件废弃了这个事件,选择了focusout。
- focus:在元素获得焦点时触发。这个事件不会冒泡,所有浏览器都支持它。
- focusin:在元素获得焦点时触发。这个事件与focus等价,但它冒泡(从名字也可以看出来,多了一个in,也就是说内部的元素获得节点,父元素同样也会执行事件处理程序)。支持该事件的浏览器有:IE5.5+、Safari5.1+、Opera11.5+和Chrome。
- focusout:在元素失去焦点时触发。这个事件是HTML事件blur的通用版本。支持该事件的浏览器有:IE5.5+、Safari5.1+、Opera11.5+和Chrome。(那么问题来了,它冒泡吗?)
- 要确定浏览器是否支持这些事件(除了DOMFocusIn和DOMFocusOut),可以使用下面的代码:
var isSupported = document.implementation.hasFeature("FocusEvent", "3.0")
- 这一类事件中最主要的两个是focus和blur。它们都是JavaScript早期就得到所有浏览器支持的事件。但这两个事件不冒泡,因此IE和Opera就分别为他们创建了focusin、focusout 和 DOMFocusIn、DOMFocusOut。IE的方式最后被DOM3级事件采纳为标准方式。
- 由于这本书编写已经过去好几年了,所有书上说的未必对(因为后期浏览器可以修正)。
- 书上说当焦点从页面的一个元素移动到另外一个元素,会依次触发以下事件:
- focusout 在失去焦点的元素上触发;
- focusin 在获得焦点的元素上触发;
- blur 在失去焦点的元素上触发;
- DOMFocusOut 在失去焦点的元素上触发;
- focus 在获得焦点的元素上触发;
- DOMFocusIn 在获得焦点的元素上触发。
- 以下是我的测试结果(Chrome):
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
</head>
<body>
<input type="text" id="input1">
<input type="text" id="input2">
<script type="text/javascript">
function test(e) {
console.log(e.target.id +":"+ e.type);
}
var input1 = document.getElementById("input1");
input1.addEventListener("blur", test);
input1.addEventListener("focus", test);
input1.addEventListener("focusin", test);
input1.addEventListener("focusout", test);
input1.addEventListener("DOMFocusOut", test);
input1.addEventListener("DOMFocusIn", test);
var input2 = document.getElementById("input2");
input2.addEventListener("blur", test);
input2.addEventListener("focus", test);
input2.addEventListener("focusin", test);
input2.addEventListener("focusout", test);
input2.addEventListener("DOMFocusOut", test);
input2.addEventListener("DOMFocusIn", test);
</script>
</body>
</html>
---------------------------
从input1框中去点击input2,打印结果如下:
input1:blur
input1:focusout
input1:DOMFocusOut
input2:focus
input2:focusin
input2:DOMFocusIn
所有没事别混起来用。基本上blur和focus就够用了。
- 下面是我做的一个关于验证blur和focus不会冒泡,focusin和focusout回冒泡的一个实验。
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
</head>
<body>
<div id="father">
<input type="text" id="input1">
<input type="text" id="input2">
</div>
<script type="text/javascript">
function test(e) {
console.log(e.currentTarget.id +":"+ e.type);
}
var input1 = document.getElementById("input1");
var input2 = document.getElementById("input2");
var father = document.getElementById("father");
input1.addEventListener("blur", test);
input1.addEventListener("focus", test);
input2.addEventListener("focusin", test);
input2.addEventListener("focusout", test);
father.addEventListener("blur", test);
father.addEventListener("focus", test);
father.addEventListener("focusin", test);
father.addEventListener("focusout", test);
</script>
</body>
</html>
------------------------------------
1.当我点击input1时,会出现
input1:focus
father:focusin
可见focusin真的会冒泡,focus不会冒泡
2.当我继续1的步骤再点击input2时,会出现
input1:blur
father:focusout
input2:focusin
father:focusin
可见blur也不会冒泡,focusout会冒泡
- 注意,虽然blur和focus没有冒泡阶段,但是依旧可以在捕获阶段捕获。所以我们可以让父元素在子元素获得或失去焦点时做一些事情。但是这样有点麻烦,需要指定事件处理程序在捕获阶段执行。而支持这样写法的浏览器基本都支持focusin和focusout。所以总的来说还是不要画蛇添足了。