目录
1、event.stopPropagation() 方法——阻止事件冒泡
2、event.preventDefault() 方法——防止默认事件
前言
本文会用到 EventUtil 事件处理程序,详情请戳:js 跨浏览器事件处理程序脚本_weixin79893765432...的博客-CSDN博客
本文包含 EventUtil 跨浏览器事件处理程序的部分完善过程。
JavaScript 与 HTML 之间交互是通过事件实现的。事件就是用户或浏览器自身执行的某种动作,是文档或浏览器窗口中发生的一些特定的交互瞬间。
我们可以使用侦听器或处理程序来预定事件,以便事件发生时执行相应的代码。
零、阻止事件冒泡 & 取消默认事件
1、event.stopPropagation() 方法——阻止事件冒泡
阻止事件冒泡到父元素:
- 普通浏览器使用:event.stopPropagation()
- IE 浏览器使用:event.cancelBubble = true
【拓展】可以使用 event.isPropagationStopped() 方法来检查某个事件是否阻止了冒泡。
2、event.preventDefault() 方法——防止默认事件
event.preventDefault() 方法用来防止默认事件生效。
一、事件流
当触发某个事件时,事件从子元素到父元素或者从父元素到子元素的过程叫做事件流。
IE 和 Netscape 开发团队分别提出了完全相反的事件流:IE 的事件流是冒泡流;Netscape 的事件流是捕获流。
DOM 2 级规定的事件流,包括三个阶段:
- 事件捕获阶段
- 目标阶段
- 事件冒泡阶段
1、事件冒泡
IE 事件流叫做事件冒泡:事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。
IE8 及其更早版本只支持冒泡流。
例如:
<!DOCTYPE html>
<html>
<head>
<title>Document</title>
</head>
<body>
<div id="myDiv"></div>
</body>
</html>
如果你单击了页面中的 <div> 元素,在事件冒泡里,这个 click 事件就会按照如下图顺序传播:
事件冒泡的演示:
/* style 中 */
<style>
.parent{
width: 400px;
height: 400px;
background: #008000;
}
.son{
display: block;
width: 180px;
height: 180px;
background: #f0f;
margin: 5px;
}
</style>
/* html 中 */
<div class="parent" id="parent">
<span class="son" id="son"></span>
</div>
/* JavaScript 中 */
<script>
var parent = document.getElementById("parent");
var son = document.getElementById("son");
son.onclick = function(){
alert("son 被触发");
}
parent.onclick = function(){
alert("parent 被触发");
}
document.body.onclick = function(){
alert("body 被触发");
}
document.onclick = function(){
alert("document 被触发");
}
window.onclick = function(){
alert("window 被触发");
}
</script>
我只点击紫红色的 son 元素一下,事件就开始冒泡了,请看效果:
阻止事件冒泡
son.onclick = function(e){
e = e || window.event;
// 阻止事件冒泡(兼容 IE 8 及其以下)
e.stopPropagation ? e.stopPropagation() : e.cancelBubble = true;
alert("son 被触发");
}
2、事件捕获
Netscape 的事件流叫做事件捕获:越不太具体的节点越先接收事件,越具体的节点越后接收事件。事件补货的用意在于在事件到达预定目标之前捕获它。
例如:
<!DOCTYPE html>
<html>
<head>
<title>Document</title>
</head>
<body>
<div id="myDiv"></div>
</body>
</html>
如果你单击了页面中的 <div> 元素,在事件捕获里,这个 click 事件就会按照如下图顺序传播:
事件捕获的演示:
/* style 中 */
.parent{
width: 400px;
height: 400px;
background: #008000;
margin-left:400px;
}
.son{
display: block;
width: 180px;
height: 180px;
background: #f0f;
margin: 5px;
}
/* html 中 */
<div class="parent" id="parent">
<span class="son" id="son"></span>
</div>
/* JavaScript 中 */
<script>
var parent = document.getElementById("parent");
var son = document.getElementById("son");
window.addEventListener("click", function(){
alert("window 被触发");
}, true);
document.addEventListener("click", function(){
alert("document 被触发");
}, true);
document.body.addEventListener("click", function(){
alert("body 被触发");
}, true);
parent.addEventListener("click", function(){
alert("parent 被触发");
}, true);
son.addEventListener("click", function(){
alert("son 被触发");
}, true);
</script>
上述代码中, addEventListener() 方法的第三个参数决定了事件的方式——捕获或者冒泡,true为捕获,false为冒泡,默认为false。后面会细讲 addEventListener() 方法的。
我只点击紫红色的 son 元素一下,事件就开始捕获了,请看效果:
阻止事件捕获
window.addEventListener("click", function(e){
e = e || window.event;
e.stopPropagation();// 阻止事件捕获
alert("window 被触发");
}, true);
由上述代码可知:stopPropagation() 方法可以阻止事件的进一步传播(包括 冒泡 和 捕获)。
3、DOM 事件流
DOM2 事件流包括三个阶段:事件捕获阶段、处于目标阶段 和 事件冒泡阶段。
DOM2 明确规定:事件捕获仅仅为截获事件提供机会,不会接收到事件目标。多数支持 DOM 事件流的浏览器都实现了一种特定的行为,但仍有一些浏览器依然支持“事件捕获和事件冒泡都可以接收到事件。”
IE8 及其更早版本不支持 DOM 事件流。
例如:
<!DOCTYPE html>
<html>
<head>
<title>Document</title>
</head>
<body>
<div id="myDiv"></div>
</body>
</html>
如果你单击了页面中的 <div> 元素,在 DOM 事件流中,这个 click 事件就会按照如下图顺序传播:
二、事件处理程序
响应某个事件的函数叫做事件处理程序(或事件侦听器)。
1、HTML 事件处理程序(了解)
一个元素支持的每种事件,都可以用一个与相应事件处理程序同名的 HTML 特性来制定。这个特性的值应该是能够执行的 JavaScript 代码。比如:
<input type="button" onClick="alert('hello world')"/>
上述代码,通过指定 onClick 特性,将要执行的具体动作作为它的值来定义。当然,也可以选择调用在页面其他地方自定义的脚本,比如:
<input type="button" onClick="showMessage()"/>
<script>
function showMessage(){
alert("Hello world");
}
</script>
HTML 事件处理程序存在三大问题:
- 时差问题:因为用户可能会在 HTML 元素已出现在页面上就出发相应的时间,但当时的事件处理程序有可能尚不具备执行条件。为此,一般会把 HTML 处理程序封装在一个 try-catch 语句中,以便在错误显示在页面之前拦截捕获它,这样用户就不会看到错误了(开发人员可自行设置抛出此类错误)。
- 不同的浏览器不同的结果:由于浏览器解析规则不一致,很可能会解析出不同的结果。
- HTML 与 JavaScript 代码紧密耦合:如果要更换事件处理程序,就必须同时改动 HTML 和 JavaScript 代码,这正是很多开发人员抛弃 HTML 事件处理程序的原因。
2、DOM0 事件处理程序(了解)
DOM0 事件处理程序就是讲一个函数 赋值 给一个事件处理程序属性。
优点是:简单、能够跨浏览器执行。
缺点是:同一个事件处理程序添加多次,后添加的会覆盖之前的。
// html 中
<div id="myDiv" style="width:100px; height:200px;"></div>
// DOM0 事件处理程序
var myDiv = document.getElementById("myDiv");
// 添加 DOM0 事件处理程序
myDiv.onmouseenter = function(){
console.log("Hello world"); // "Hello world"
};
myDiv.onmouseout = function(){
console.log("Bye-bye");
}
myDiv.onmouseout = function(){
console.log("Get out"); // "Get out"
}
// 删除 DOM0 事件处理程序
myDiv.onmouseenter = null;
myDiv.onmouseout = null;
3、DOM2 事件处理程序
DOM2 定义了两个操作事件处理程序的方法:
- addEventListener():添加事件处理程序,同一个事件处理程序可以添加多次。接受三个参数,分别是 要处理的事件名、作为事件处理程序的函数 和 (可选)一个布尔值。这个布尔值默认为 false,如果是 true,表示在捕获阶段调用事件处理程序;如果是 false,表示在冒泡阶段调用事件处理程序。另外,在事件处理程序的内部 this 始终指向 currentTarget,即添加事件处理程序的调用者。
- removeEventListener():删除事件处理程序。(参数特性与 addEventListener() 方法一样),使用 addEventListener() 方法添加的事件处理程序必须用 removeEventListener() 方法来删除,而且删除时传入的参数与添加事件处理程序时的参数必须完全一致。这意味着通过 addEventListener() 添加的匿名函数将无法被移除,所以我们必须规避掉这个大坑。
比如:
// html 中
<input id="btn" type="button" value="提交">
// JavaScript 中
var btn = document.getElementById("btn");
btn.addEventListener("click", function(){
alert("hello world"); // hello world
});
btn.removeEventListener("click", function(){
alert("hello world");
});
// 删除失败,click 事件依然有效。改成下面这样写就能删除 click 事件了:
var handler = function(){
alert("hello world");
};
btn.addEventListener("click", handler);
btn.removeEventListener("click", handler);
4、IE 事件处理程序
IE 实现了与 DOM 类似的两个方法:
- attachEvent():添加事件处理程序,同一个事件处理程序可以添加多次。接受两个参数,分别是 事件处理程序名称 和 事件处理程序函数。使用该方法添加事件处理程序与 DOM 有 4 大区别:
- 只会将事件处理程序添加到冒泡阶段,这是因为 IE8 及其更早版本只支持冒泡流。
- 第一个参数必须写成 “on” + 事件类型 的形式。
- this 指向 window,而不是事件处理程序调用者。
- 执行顺序是“先添加的后执行”,类似于栈存储方式(先进后出)。DOM 是先添加的先执行,类似于队列存储方式(先进先出)。
- detachEvent():删除事件处理程序。(参数特性与 attachEvent() 方法一样。)使用 attachEvent() 方法添加的事件处理程序必须用 detachEvent() 方法来删除,而且删除时传入的参数与添加事件处理程序时的参数必须完全一致。这意味着通过 attachEvent() 添加的匿名函数将无法被移除,所以我们必须规避掉这个大坑。这里与 DOM 的 removeEventListener() 方法原理一致,不再举例。
5、跨浏览器的事件处理程序
想要跨浏览器实现事件处理程序,目前有两种途径:
- 一个是使用能够隔离浏览器差异的 JavaScript 库。
- 另一个是自己编码实现(要恰当的使用“能力检测”来检测浏览器的能力)。
要保证事件处理程序能在大多数浏览器下一致的运行,只需要关注冒泡阶段!!!
下面列举一个跨浏览器实现事件处理程序的案例:
// 封装一个跨浏览器的事件处理程序对象
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.detachEvent){
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
}
}
// 使用这个对象添加、删除跨浏览器事件处理程序
var handler = function(){
alert(hello world);
};
EventUtil.addHandler(btn, "clcik", handler);
EventUtil.removeHandler(btn, "clcik", handler);
不过,上述实现方案仍然存在以下问题:
- IE 添加事件处理程序的作用域问题;
- DOM0 对每个事件只支持一个事件处理程序,否则会发生覆盖。
三、事件对象
触发某个事件时生成的对象叫做事件对象。事件对象包含着所有与事件相关的信息。
1、DOM 中的事件对象
DOM 中的事件对象是 event 对象。
在 DOM0 中 event 对象是 window 对象的一个属性,在 DOM2 中 event 对象就是 event 对象。
event 对象有以下属性:
属性 | 类型 | 读/写 | 描述 |
---|---|---|---|
detail | Integer | 只读 | 与事件相关的细节信息。 |
target | Element | 只读 | 事件的目标。 |
currentTarget | Element | 只读 | 其事件处理程序当前正在处理事件的那个元素。 |
type | String | 只读 | 被触发的事件类型。 |
cancelable | Boolean | 只读 | 表明是否可以取消事件的默认行为。 |
defaultPrevented | Boolean | 只读 | 为 true 表示已经调用了 preventDefault() 方法。(DOM3 新增) |
eventPhase | Integer | 只读 | 调用事件处理程序的阶段:1 表示捕获阶段;2 表示目标阶段;3 表示冒泡阶段。 |
bubbles | Boolean | 只读 | 表明事件是否冒泡。 |
view | AbstractView | 只读 | 与时间关联的抽象视图。等同于发生事件的 window 对象。 |
trusted | Boolean | 只读 | 为 true 表示事件是浏览器生成的;为 false 表示事件是由开发人员通过 JavaScript 创建的。(DOM3 新增) |
event 对象有以下方法:
- preventDefault():取消事件的默认行为。必须设置 cancelable 为 true,才可以使用该方法。
- stopPropagation():取消事件的进一步捕获或冒泡。必须设置 bubbles 为 true,才可以使用该方法。
- stopImmediatePropagation():取消事件的进一步捕获或冒泡,同时组织任何事件处理程序被调用(DOM3 新增方法)。
2、IE 中的事件对象
IE 的 event 对象有以下属性:
IE 浏览器中,根据添加事件处理程序的不同方式,访问 event 对象的方式分为三种:
- 用 DOM0 添加事件处理程序时,event 对象作为 window 对象的一个属性存在。
- 用 attachEvent() 方法添加事件处理程序时,event 对象作为参数被传入事件处理程序函数中。
- 用 HTML 特性添加事件处理程序时,event 对象作为 event 变量存在。
// DOM0
var btn = document.getElementById("myBtn");
btn.onclick = function(){
var event = window.event;
alert(event); // "click"
};
// attachEvent 方法
var btn = document.getElementById("myBtn");
var handle = function(event){
alert(event); // "click"
}
btn.addachEvent("onclick", handle);
// HTML
<input type="button" onclick="alert(event.type)"> // "click"
3、跨浏览器的事件对象
// EventUtil.js 封装
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.detachEvent){
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
},
// 获取 event 对象
getEvent: function(){
return event ? event : window.event;
},
// 获取事件目标
getTarget: function(){
return event.target || event.srcElement;
},
// 取消默认事件行为
preventDefault: function(){
if(event.preventDefault){
event.preventDefault();
} else {
event.returnValue = false;
}
},
// 阻止事件冒泡
stopPropagation: function(){
if(event.stopPropagation){
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
}
四、事件类型
事件类型大致可分为:
- DOM 事件
- UI 事件:当用户与页面上的元素交互时触发。
- 焦点事件:元素获得或失去焦点。
- 鼠标事件:通过鼠标在页面上执行操作。
- 滚轮事件:使用鼠标滚轮或类似设备。
- 文本事件:当用户在文档中输入文本。
- 键盘事件:通过键盘在页面上执行操作。
- 合成事件:当为 IME(输入法编辑器)输入字符时触发。
- 变动事件:底层 DOM 结构发生变化。
- HTML5 事件:DOM 规范没有涵盖所有浏览器支持的所有事件,HTML5 详尽列出了浏览器应该支持的所有事件。
- 专有事件
- 设备事件:针对手机、平板等各种设备的事件。
- 触摸与手势事件
一般,在 window 上触发的任何事件都可以在 <body> 元素中通过相应的特性来指定。
DOM3 级事件是在 DOM0 级和 DOM2 级事件的基础上重新定义了DOM 的事件。包括 IE 9 在内的所有主流浏览器都支持 DOM2 级和 DOM3 级的事件。
1、UI 事件
- load 事件:页面完全加载完后(所有图像、js 文件、css 文件等)在 window 上触发;所有框架加载完毕后在框架集上触发;图像加载完毕在 img 元素上触发;当嵌入内容加载完毕在 <object> 元素上触发。
- unload 事件:页面完全卸载在 window 上触发;所有框架都卸载后在框架集上触发;嵌入内容卸载完毕后在 <object> 元素上触发。
- abort 事件:当用户停止下载过程,如果嵌入内容没有加载完,则在 <object> 元素上触发。
- error 事件:当js错误时在 window 上触发;当一或多个框架无法加载在框架集上触发;当无法加载图像时在 img 元素上触发;当无法加载嵌入内容时在 <object> 元素上触发。
- select 事件:当用户选择文本框(<input> 或 <texterea>)中的一个或多个字符时触发。
- resize 事件:当窗口或框架的大小变化时在 window 或 框架 上触发。
- scroll 事件:当用户滚动带滚动条的元素中的内容时,在该元素上触发。<body> 元素中包含所加载页面的滚动条。
如何检测浏览器是否支持 DOM2 级或 DOM3 级规定的 HTML 事件呢?
var isSupported = document.implementation.hasFeature("HTMLEvents", "2.0");
// 或
var isSupported = document.implementation.hasFeature("HTMLEvents", "3.0");
只有根据 DOM2 级或 DOM3 级事件实现这些事件的浏览器才会返回 true,而以非标准方式支持这些事件的浏览器则会返回 false。
(1)、load 事件
当页面完全加载完毕后(所有图像、js文件、css文件等),就会触发window上面的load事件。
这个事件在 window 上面触发,因此,可以通过 JavaScript 或者 <body> 元素中的 onresize 特性来指定事件处理程序:
--> 第一种方式是使用 JavaScript 的跨浏览器事件处理程序:
EventUtil.addHandler(window, "load", function(event){
alert("Loaded");
});
--> 第二种方式是为 <body> 元素添加一个 onload 特性:
<!DOCTYPE html>
<html lang="en">
<head>
<title>load 事件案例</title>
</head>
<body onload="alert('Loaded')">
</body>
</html>
其实,在 DOM2 级事件规范中,应该在 document 而非 window 对象上触发 load 事件,但是,所有的浏览器都在 window 上实现了该事件,以确保向后兼容。另外,还有一些非标准的方式支持 load 事件,比如:在 IE9+、Firefox、Opera、Chrome 和 Safari3+ 中,<script> 元素也支持 load 事件,以便开发人员确定动态加载的 JavaScript 文件是否已经加载完毕。
(2)、unload 事件
这个事件在文档被完全卸载后触发。只要用户从一个页面切换到另一个页面,就会发生unload事件。
这个事件在 window 上面触发,因此,可以通过 JavaScript 或者 <body> 元素中的 onresize 特性来指定事件处理程序(与 load 事件类似,不赘述)。
(3)、resize 事件
当浏览器窗口被调整到一个新的高度或宽度,就会触发该事件。
这个事件在 window 上面触发,因此,可以通过 JavaScript 或者 <body> 元素中的 onresize 特性来指定事件处理程序(与 load 事件类似,不赘述)。
(4)、scroll 事件
该事件虽然在 window 对象上发生的,但实际表示的是页面中相应元素的变化。
- 混杂模式下,通过 <body> 元素的 scrollLeft 和 scrollTop 来监控变化;
- 标准模式下,Safari3.1 及其以下的版本仍然基于 <body> 元素的 scrollLeft 和 scrollTop 来监控变化,除 Safari 之外的所有浏览器都会通过 <html> 元素(documentElement)来反映这个变化。
举个栗子:
EventUtil.addHandler(window, "scroll", function(event){
if(document.compatMode == "CSS1Compat"){
alert(document.documentElement.scrollTop);
}else{
alert(document.body.scrollTop);
}
});
上述代码中: compatMode 属性,反应浏览器采用了哪种渲染模式。
- 标准模式下,document.compatMode 的值等于“CSS1Compat”;
- 混杂模式下,document.compatMode 的值等于“BackCompat”。
2、焦点、鼠标与滚轮事件
(1)、焦点事件
焦点事件会在页面元素获得或失去焦点的时候触发。利用这些事件 与 document.hasFocus() 方法 和 document.activeElement 属性配合,可以知晓用户在页面上的行踪。
- blur 事件:在元素失去焦点时触发,不冒泡。注意与本文下面介绍的 change 事件区分(js 事件_weixin79893765432...的博客-CSDN博客)
- focus 事件:在元素获得焦点时触发,不冒泡。
- focusin 事件:在元素获得焦点时触发,冒泡,IE5.5+、Safari5.1+、Opera11.5+ 和 Chrome 支持。
- focusout 事件:在元素失去焦点时触发,冒泡,IE5.5+、Safari5.1+、Opera11.5+ 和 Chrome 支持。
IE 早期版本支持 focusin 和 focusout 事件的方式被 DOM3 纳入了标准,那么如何检测浏览器是否支持 DOM3 级规定的鼠标焦点事件呢?
var isSupported = document.implementation.hasFeature("FocusEvent", "3.0");// 注意:DOM3 级事件的 feature 名是 MouseEvent 而不是 MouseEvents。
只有根据 DOM3 级事件实现这些事件的浏览器才会返回 true,而以非标准方式支持这些事件的浏览器则会返回 false。
(2)、鼠标事件
- click 事件:在鼠标单击(一般是左击)时会触发,按回车键时也会触发,冒泡。
- dblclick 事件:在鼠标双击(一般是左击)时会触发,冒泡。
- mousedown 事件:用户按下任意鼠标按钮时触发,冒泡。
- mouseup 事件:用户释放鼠标按钮时触发,冒泡。
- mousemove 事件:鼠标在指定元素内部,只要移动就会触发,冒泡。
- mouseover 事件:鼠标移入时触发,光标移动到后代元素上会触发(本质是只要 event.target 目标改变就会触发),冒泡。
- mouseout 事件:鼠标移出时触发,光标移动到后代元素上会触发(本质是只要 event.target 目标改变就会触发),冒泡。
- mouseenter 事件:鼠标移入时触发,鼠标移动到后代元素上不会触发,不冒泡。
- mouseleave 事件:鼠标移出时触发,鼠标移动到后代元素上不会触发,不冒泡。
①、如何检测浏览器是否支持 DOM2 级或 DOM3 级规定的鼠标事件(除 dbclick、mouseenter 和 mouseleave 之外)呢?
var isSupported = document.implementation.hasFeature("MouseEvents", "2.0");
// 或
var isSupported = document.implementation.hasFeature("MouseEvent", "3.0");// 注意:DOM3 级事件的 feature 名是 MouseEvent 而不是 MouseEvents。
只有根据 DOM2 级或 DOM3 级事件实现这些事件的浏览器才会返回 true,而以非标准方式支持这些事件的浏览器则会返回 false。
②、click 与 dbclick 事件的工作原理
只有在同一个元素上相继触发 mousedown 和 mouseup 事件,才会触发 click 事件,如果 mousedown 或 mouseup 中的一个没取消了,就不会触发 click 事件了。
只有快速触发两次 click 事件,才会触发 dbclick 事件。如果有代码阻止了连续两次触发 click 事件(可能是直接取消 click 事件,也可能通过取消 mousedown 和 mouseup 间接实现),那么就不会触发 dbclick 事件了。
③、click 事件获取指定的坐标
--> 客户区坐标位置——clientX 与 clientY 属性
通过客户区坐标 clientX 与 clientY 属性,能够知道鼠标事件是在视口中什么位置发生的。
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function(event){
event = EventUtil.getEvent(event);
alert(`客户坐标:${event.clientX},${event.clientY}`);
});
--> 页面坐标位置——pageX 与 pageY 属性
通过页面坐标 pageX 与 pageY 属性,能够知道鼠标事件是在页面中什么位置发生的。
在页面没有滚动的情况下,pageX 和 pageY 的值与 clientX 与 clientY 的值相等。
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function(event){
event = EventUtil.getEvent(event);
alert(`页面坐标:${event.pageX},${event.pageY}`);
});
在 IE8 及更早版本中不支持 pageX 与 pageY 属性,不过使用客户区坐标(clientX 与 clientY 属性)可以计算出来,需要用到 document.body(混杂模式)或 document.documentElement(标准模式)中的 scrollLeft 和 scrollTop 属性,计算过程如下:
if (event.pageX === undefined) {
clickX = event.clientX + (document.body.scrollLeft || document.documentElement.scrollLeft);
};
if (event.pageY === undefined) {
clickY = event.clientY + (document.body.scrollTop || document.documentElement.scrollTop);
};
--> 屏幕坐标位置——screenX 与 screenY 属性
通过屏幕坐标 screenX 与 screenY 属性,能够知道鼠标事件是在电脑屏幕中什么位置发生的。
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function(event){
event = EventUtil.getEvent(event);
alert(`屏幕坐标:${event.screenX},${event.screenY}`);
});
④、探索 mouseover、mouseout、mouseenter 和 mouseleave 这些事件在子元素上是否会触发:
div.onmouseover = function() {
console.log('Mouseover 触发'); //在子元素上会触发
}
div.onmouseout = function() {
console.log('Mouseout 触发'); //在子元素上会触发
}
div.onmouseenter = function() {
console.log('Mouseenter 触发'); //在子元素上不会触发
}
div.onmouseleave = function() {
console.log('Mouseleave 触发'); //在子元素上不会触发
}
由上述代码可知,mouseover 和 mouseout 事件会在子元素上触发,为了方便获取 mouseover 和 mouseout 事件的动向,DOM 给 event 对象提供了一个 relatedTarget 属性,这个属性返回与事件的目标节点相关的节点。IE8及其以下版本不支持 relatedTarget 属性,在 mouseover 事件触发时 IE 提供了 fromElement 属性替代 relatedTarget 属性;在 mouseout 事件触发时 IE 提供了 toElement 属性替代 relatedTarget 属性。可以把下面代码添加到 EventUtil 对象中:
var EventUtil = {
// 省略了其它代码
// 获取与事件的目标节点相关的节点(只对 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;
}
},
// 省略了其它代码
}
可以像下面这样使用 EventUtil.getRelatedTarget() 方法:
/* css 中 */
<style>
#myDiv{
width: 200px;
height: 200px;
background: #00f000;
position: relative;
}
#mySpan{
display: block;
width: 80px;
height: 80px;
background: #f0f;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
}
</style>
/* html 中 */
<div id="myDiv">
<span id="mySpan"></span>
</div>
/* JavaScript 中 */
<script src="./EventUtil.js"></script>
<script>
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "mouseover", function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
var relatedTarget = EventUtil.getRelatedTarget(event);
console.log(`鼠标从 ${relatedTarget.tagName} 移入至 ${target.tagName}`);
});
EventUtil.addHandler(div, "mouseout", function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
var relatedTarget = EventUtil.getRelatedTarget(event);
console.log(`鼠标从 ${target.tagName} 移出至 ${relatedTarget.tagName}`);
});
</script>
效果:
⑤、探索鼠标点击 button 按钮时对 mousedown 和 mouseup 事件的影响
由于只要在同一个元素上相继触发 mousedown 和 mouseup 事件就会触发 click 事件,所以,当我们单击时,会先触发 mousedown 和 mouseup 事件。 对于 mousedown 和 mouseup 事件,DOM 给 event 对象提供了一个 button 属性(注意不是 <button> 或者 <input type="button"> 元素哦)。
DOM 中 event.button 属性有三个值:
- 0:主鼠标按钮,一般是左键。
- 1:鼠标滚轮按钮。
- 2:次鼠标按钮,一般是右键。
IE8 及之前的版本中,event.button 属性的值与 DOM 中的差异很大:
- 0:表示没有按下按钮。
- 1:表示按下了主鼠标按钮。
- 2:表示按下了次鼠标按钮。
- 3:表示同时按下了主次鼠标按钮。
- 4:表示按下了中间的鼠标按钮。
- 5:表示同时按下了主鼠标按钮和中间鼠标按钮。
- 6:表示同时按下了次鼠标按钮和中间鼠标按钮。
- 7:表示同时按下了三个鼠标按钮。
为了兼容 IE 必须另辟蹊径。我们可以用 hasFuture() 方法来检测,可以把下面代码添加到 EventUtil 对象中:
EventUtil = {
// 省略了其它代码
// 鼠标按钮事件
getButton: function(event){
if(document.implementation.hasFeature("MouseEvents", "2.0")){
return event.button;
}else{// 兼容 IE
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;
}
}
}
// 省略了其它代码
}
可以像下面这样使用 EventUtil.getButton() 方法:
/* css 中 */
<style>
#myDiv{
width: 80px;
height: 40px;
background: #ddd;
border: 1px solid #333;
border-radius: 5px;
}
</style>
/* html 中 */
<div id="myDiv"></div>
/* JavaScript 中 */
<script>
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "mousedown", function(event){
event = EventUtil.getEvent(event);
console.log(EventUtil.getButton(event));
});
EventUtil.addHandler(div, "mouseup", function(event){
event = EventUtil.getEvent(event);
console.log(EventUtil.getButton(event));
});
</script>
效果:
点击主鼠标按钮:00
点击滚轮按钮:11
点击次鼠标按钮:22
⑥、键盘上的修改键协同鼠标触发 鼠标事件
鼠标事件主要是鼠标来触发的,其实一些键盘上的修改键也可以影响到鼠标事件的行为,这些修改键包括 Shift、Ctrl、Alt、Meta(window 上的 Windows 键 / 苹果机上的 Cmd 键)。
修改键的状态分别是 shiftKey、ctrlKey、altKey、metaKey(IE8 及其以前的版本不支持),这些属性包含的都是布尔值,如果相应的键被按下了,则为 true,否则为 false。
当某个鼠标事件发生时,通过检测这几个属性就可以确定用户是否同时按下了其中的键。
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function(event){
event = EventUtil.getEvent(event);
var keys = [];
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.join(",")}`);
});
⑦、event.detail 属性为鼠标单击事件计数
DOM2 级事件提供了 detail 属性,用于给出有关事件的更多信息。对于鼠标事件来说,detail 中包含了一个数值,表示在给定位置上发生了多少次单击。detail 属性从 1 开始计数,每次单击发生后都会递增。如果鼠标在 mousedown 和 mouseup 之间移动了位置,则 detail 会被重置为 0。
(3)、滚轮事件
当用户通过鼠标滚轮与页面交互时就会触发 mousewheel 事件,这个事件可以在页面内任何元素上触发,最终会冒泡到 document(IE8)或 window(IE9、Opera、Chrom 及 Safari)对象。与 mousewheel 事件对应的 event 对象有一个 wheelDelta 属性。
wheelDelta 属性:当用户向前(向上)滚动滚轮时,wheelDelta 是 120 的倍数;当向后(向下)滚动滚轮时,wheelDelta 是 -120 的倍数。通过监听 mousewheel 事件的 wheelDelta 属性判断鼠标向那个方向滚动了:
EventUtil.addHandler(document, "mousewheel", function (event) {
e = EventUtil.getEvent(event);
if (e.wheelDelta) { //判断浏览器IE,谷歌滑轮事件
if (e.wheelDelta > 0) { //当滑轮向上滚动时
alert('上滚')
}
if (e.wheelDelta < 0) { //当滑轮向下滚动时
alert('下滚')
}
} else if (e.detail) { //Firefox滑轮事件
if (e.detail> 0) { //当滑轮向下滚动时
alert('下滚')
}
if (e.detail< 0) { //当滑轮向上滚动时
alert('上滚')
}
}
});
另外,在 Opera 9.5 之前的版本中,wheelDelta 值的正负号是颠倒的。在 Firefox 中,当鼠标滚动滚轮时,支会触发一个名为 DOMMouseScroll 的滚轮事件,而有关鼠标的滚轮的信息则保存在 detail 属性中,当鼠标向前滚动时,这个属性的值是 -3 的倍数,当鼠标向后滚动时,这个属性的值是 3 的倍数。
综上,鼠标滚轮事件的跨浏览器版代码也可以添加进 EventUtil 对象里面了:
<script src="./client.js"></script>
<script>
var EventUtil = {
// 省略了其它代码
// 鼠标滚轮事件
getWheelDelta: function(event){
if(event.wheelDelta){
return (client.engine.opera && client.engine.opera < 9.5 ? -event.wheelDelta : event.wheelDelta);
} else {
return -event.detail * 40;
}
},
// 省略了其它代码
}
</script>
可以像下面这样使用 EventUtil.getWheelDelta() 方法:
(function(){
function handleMouseWheel(event){
event = EventUtil.getEvent(event);
var delta = EventUtil.getWheelDelta(event);
alert(delta);
}
EventUtil.addHandler(document, "mousewheel", handleMouseWheel);
EventUtil.addHandler(document, "DOMMouseScroll", handleMouseWheel);
})();
上述代码,将相关代码放在了私有作用域中,从而不会影响全局作用域。
3、键盘与文本事件
“DOM3级事件”为键盘事件制订了规范:
- 键盘事件:
- keydown:按下任意键触发,如果按住不放,会重复触发。
- keypress:按下字符键触发,如果按住不放,会重复触发;按下Esc键也会触发。
- keyup:释放键盘上的键时触发。
- 文本事件:
- textInput:在文本插入文本框之前会触发 textInput 事件。textInput 是对 keypress 的补充,用意是在将文本显示给用户之前更容易拦截文本。
- input 事件:在用户输入时触发,只要元素值发生变化就会立即触发。
- change 事件:当输入框失焦的时候触发。注意与本文上面介绍的 blur 事件区分(js 事件_weixin79893765432...的博客-CSDN博客)
- 拓展:input 框所有事件的整个过程:
获取焦点:focus -> 键盘输入:keydown -> keypress -> keyup -> input -> 失去焦点: change -> blur
- 拓展:input 框所有事件的整个过程:
(1)、键码
键码表请戳这里:计算机编码那些事儿_weixin79893765432...的博客-CSDN博客
(2)、字符编码
IE9、Firefox、Chrome 和 Safari 浏览器的 event 对象都支持一个 charCode 属性,IE8 及之前的版本和 Opera 浏览器则是在 keyCode 中保存字符的 ASCII 编码。为了实现跨浏览器获取字符编码,我们可以将下面代码添加到 EventUtil 对象中:
var EventUtil = {
// 省略的代码
getCharCode: function(){
if(typeof event.charCode == "number"){
return event.charCode;
}else{
return event.keyCode;
}
}
// 省略的代码
};
可以像下面这样使用 EventUtil.getCharCode() 方法:
var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "keypress", function(event){
event = EventUtil.getEvent(event);
alert(EventUtil.getCharCode(event));
});
上述代码中,在取得字符编码后,可以用 String.fromCharCode() 方法将其转换成实际的字符。
(3)、DOM3 级键盘事件的变化
①、key 和 char 属性(兼容 keyIdentifier 属性)
DOM3级事件中的键盘事件,不再包含charCode属性,而是包含两个新属性:key 和 char。
- key 属性:其值是个字符串。按下字符键时 key 值是该键的文本字符;按下非字符键时 key 值是相应键的名。
- char 属性:按下字符键时 key 值是该键的文本字符;按下非字符键时 key 值是 null。
兼容性:
- IE9 支持 key 属性但不支持 char 属性;
- Safari5 和 Chrome 支持名为 keyIdentifier 的属性,对于 keyIdentifier 属性,按下字符键时返回格式类似“U+0000”的字符串,表示 Unicode 值;按下非字符键时返回相应键的名。
这样解决这个兼容问题:
var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "keypress", function(event){
event = EventUtil.getEvent(event);
var identifier = event.key || event.keyIdentifier;// 兼容
if(identifier){
alert(identifier);
}
});
②、location 属性(兼容 keyLocation 属性)
location 属性表示按下了什么位置上的键:
- 0 表示默认键盘。
- 1 表示左侧位置(左边的 Alt 键等)。
- 2 表示右侧位置(右边的 Shift 键等)。
- 3 表示数字小键盘。
- 4 表示移动设备键盘(虚拟键盘)。
- 5 表示手柄(Wii 控制器等)。
兼容性:
- IE9 支持这个属性。
- Safari 和 Chrome 支持名为 keyLocation 属性(目前 Chrome 支持 location 属性了)。
这样解决这个兼容问题:
var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "keypress", function(event){
event = EventUtil.getEvent(event);
var location = event.location || event.keyLocation;// 兼容
if(location){
alert(location);
}
});
③、getModifierState() 方法
getModifierState() 方法接收一个参数——修改键(“Shift”、“AltGraph”、“Control”或“Meta”)。当指定的修改键是活动的(也就是处于被按下状态),这个方法返回 true,否则返回 false。
实际上,通过 event 对象的 shiftKey、altKey、ctrlKey 和 metaKey 属性已经可以取得类似的属性了。
IE9 是唯一支持 getModifierState() 方法的浏览器。
(4)、textInput 事件
当用户在可编辑区域中输入字符时,就会触发 textInput 事件。
textInput 事件与 keypress 事件的区别:
- 任何能获得焦点的元素都可以触发 keypress 事件,但只有可编辑区域才能触发 textInput 事件;
- textInput 事件只会在用户按下能够输入实际字符的键时才会被触发,keypress 事件则在按下能够影响文本显示的键时也会触发(例如退格键);
①、event.data 属性
textInput 事件主要考虑的是字符,在 event 对象中包含一个 data 属性,它返回用户输入的字符(而非字符编码)。
这样使用 textInput 事件里的 event.data 属性:
var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "textInput", function(event){
event = EventUtil.getEvent(event);
alert(event.data);
});
注意:只能在 DOM2 事件处理程序——addEventListener() 方法中获取 event.data 属性的值。
②、event.inputMethod 属性
dvent对象上的属性,表示把文本输入到文本框中的方式:
- 0:不确定如何输入的;
- 1:键盘;
- 2:粘贴;
- 3:拖放;
- 4:IME;
- 5:表单中选择某一项输入;
- 6:手写;
- 7:语音;
- 8:组合输入;
- 9:脚本输入。
4、复合事件
复合事件是 DOM3 级事件中新添加的一类事件,用于处理 IME 的输入序列。
复合事件有以下三中:
- compositionstart:在 IME 的文本复合系统打开时触发,表示要开始输入了。
- compositionupdate:在向输入字段中插入新字符时触发。
- compositionend:在 IME 的文本复合系统关闭时触发,表示返回正常键盘输入状态。
复合事件的 data 属性:
- compositionstart访问data:正在编辑的文本。
- compositionupdate访问data:正插入的新字符。
- compositionend访问data:插入的所有字符。
5、变动事件
DOM2级的变动事件,能在DOM中的一部分发生变化时给出提示。
变动事件是为 XML 或 HTML DOM 设计的,并不特定于某种语言。
DOM2级定义了如下变动事件:
- DOMSubtreeModified:在DOM结构发生变化时触发,即会在下面所有事件触发后触发。
- DOMNodeInserted:当一个节点作为子节点插入另一个节点中时触发。
- DOMNodeRemoved:节点从父节点中移除时触发。
- DOMNodeInsertedIntoDocument:一个节点被直接插入文档或通过子树间接插入文档后触发,在DOMNodeInserted 事件之后触发。
- DOMNodeRemovedFromDocument:一个节点直接从文档移除,或通过子树间接从文档移除触发,在DOMNodeRemoved 事件之后触发。
- DOMAttrModified:特性被修改之后触发。
- DomCharacterDataModified:在文本节点的值发生变化时触发。
可以使用以下代码判断浏览器对变动事件的支持性:
var isSupported = document.implementation.hasFeature("MutationEvents","2.0");
(1)、删除节点
DOMNodeRemoved 和 DOMNodeRemovedFromDocument 事件。
当使用 removeChild() 或 replace Child() 从 DOM 中删除节点时:
- 首先,会触发 DOMNodeRemoved 事件。该事件的目标(event.target)是被删除的节点,event.relatedNode 则是对目标节点的父节点的引用。在该事件触发时,目标节点还尚未从其父节点移除,因此这时其 paerentNode 仍指向其父节点。该事件会冒泡,可以在 DOM 的任何层次上处理它。
- 然后,如果被移除的节点包含子节点,那么在其所有子节点及这个被移除的节点上会相继触发 DOMNodeRemovedFromDocument 事件。该事件不会冒泡,所以只有直接指定给其中一个子节点的事件处理程序才会被调用。该事件的目标(event.target)是相应的子节点或者那个被移除的节点,除此之外,该事件的 event 对象不包含其它信息。
- 最后,触发 DOMSubtreeModified 事件,该事件的目标(event.target)是被移除节点的父节点,此时的 event 对象也不会提供额外的信息。
(2)、插入节点
DOMNodeInserted 和 DOMNodeInsertedIntoDocument 事件。
在使用 appendChild()、replaceChild() 或 insertBefore() 方法向 DOM 中插入节点时:
- 首先,会触发 DOMNodeInserted 事件,该事件的目标(event.target)是被插入的节点,event.relatedNode 指向目标节点的父节点。当该事件触发时,目标元素已经插入到了新的父节点中 。该事件会冒泡,可以在 DOM 的任何层次上处理它。
- 然后,会在新插入的节点上触发 DOMNodeInsertedIntoDocument 事件。该事件不冒泡,因此必须在插入节点之前为它添加该事件的事件处理程序。该事件的目标(event.target)是被插入的节点,除此之外,该事件的 event 对象不包含其它信息。
- 最后,会触发 DOMSubtreeModified 事件,在新插入节点的父节点上触发。该事件不冒泡。该事件的目标(event.target)是新插入节点的父节点,除此之外,该事件的 event 对象不包含其它信息。
6、HTML5 事件
DOM 规范没有涵盖所有浏览器支持的所有事件,HTML5 详尽列出了浏览器应该支持的所有事件。
(1)、contextmenu 事件
一般情况下,在 Windows 中右击鼠标显示上下文菜单,在 Mac 中 Ctrl+单击显示上下文菜单。何时应该显示上下文菜单,以便开发者取消默认的上下文菜单,转而提供自定义的菜单呢?请看 contextmenu 事件:
- 该事件用来显示自定义的上下文菜单,而使用 click 事件处理程序能够隐藏该菜单。
- 该事件会冒泡,因此可以为 document 指定一个事件处理程序,用以处理页面中发生的所有类似事件。
- 该事件的目标(event.target)是发生用户操作的元素,在所有浏览器中都将可以取消这个事件。取消这个事件的代码请查看“js 跨浏览器事件处理程序脚本_weixin79893765432...的博客-CSDN博客”里面的“取消默认事件行为”。
- 该事件属于鼠标事件,所以其事件对象中包含与光标位置有关的所有属性。
- 支持该事件的浏览器:IE、Firefox、Safari、Chrome 和 Opera11+。
可以像下面这样使用 contextmenu 事件:
web 自定义上下文菜单基本结构(★★★★★)
/* html 中 */
<ul id="myMenu" style="position:absolute;visibility:hidden;background-color:silver;">
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>
/* JavaScript 中 */
<script>
EventUtil.addHandler(document, "contextmenu", function(event){
event = EventUtil.getEvent(event);
// 取消默认行为,以保证不显示浏览器默认的上下文菜单
EventUtil.preventDefault(event);
var menu = document.getElementById("myMenu");
menu.style.left = event.clientX + "px";
menu.style.top = event.clientY + "px";
menu.style.visibility = "visible";
});
EventUtil.addHandler(document, "click", function(event){
document.getElementById("myMenu").style.visibility = "hidden";
});
</script>
(2)、beforeunload 事件
beforeunload 事件是为了满足“在页面卸载之前阻止这一操作”:
- 该事件会在浏览器卸载页面之前触发,询问用户是否真的要关闭页面,还是希望继续留下来。可以通过该事件来取消卸载并继续使用原有页面。
- 在该事件中,对于 IE 和 Fiefox 而言,通过 event.returnValue 属性来设置要显示给用户的内容;对于 Safari 和 Chrome 而言,event.returnValue 属性设置的内容最终会作为函数的返回值返回。
- 支持该事件的浏览器:IE、Firefox、Safari、Chrome 和 Opera11+。
可以像下面这样使用 beforeunload 事件:
在页面关闭之前询问用户是否真的要关闭页面(★★★★★)
// 当离开时会显示信息和两个按钮,当刷新时也会提示(是否确定离开此页面)
EventUtil.addHandler(window, "beforeunload", function(event){
event = EventUtil.getEvent(event);
var message = "do you realy want to leave?";
event.returnValue = message;
return message;
});
(3)、DOMContentLoaded 事件
window 对象的 load 事件会在页面中所有资源都加载完毕时触发,但这个过程可能会因为要加载的外部资源过多而颇费周折。如何不必等待所有资源都加载完毕就能为页面添加事件处理程序呢?请看 DOMContentLoaded 事件:
- 该事件在形成完整的 DOM 树之后就会触发(在 load 事件之前触发),不理会图像、JavaScript 文件、CSS 文件或其他资源是否已经下载完毕。
- 与 load 事件不同,该事件支持在页面下载的早期添加事件处理程序,用户能够尽早地与页面交互。
- 该事件会冒泡。尽管这个事件会冒泡到 window 对象,但它的目标(event.target)实际上是 document 对象。除此之外,该事件的 event 对象不包含其它信息。
- 支持该事件的浏览器:IE9+、Firefox、Safari3.1+、Chrome 和 Opera9+。
可以像下面这样使用 beforeunload 事件:
不必等待引入的资源都加载完毕就能与页面交互(★★★★★)
EventUtil.addHandler(document, "DOMContentLoaded", function(event){
alert("内容加载完毕。");
// 与页面交互吧
});
对于不支持该事件的浏览器版本,理想情况下,可以设置一个时间为 0 毫秒的延时器,强制 load 事件允许在并未加载完所有资源的情况下与页面交互:
setTimeout(function(){
alert("内容加载完毕。");
// 在此与页面交互
}, 0);
注意:这里不保证在所有的环境下该超时调用一定会早于 load 事件被触发。
(4)、readystatechange 事件
IE 提供的事件,支持该事件的每个对象都有一个 readyState 属性,可能包含下列 5 个值中的一个:
- uninitialized(未初始化):对象存在但尚未初始化。
- loading(正在加载):对象正在加载数据。
- loaded(加载完毕):对象加载数据完成。
- interactive(交互):可以操作对象,但还没有完全加载。
- complete(完成):对象已经加载完毕。
并非每个对象都会经历 readyState 属性的这些阶段。
对于 document 对象,值为 interactive 的 readyState 会在与 DOMContentLoaded 大致相同的时刻触发 readystatechange 事件。
readystatechange 事件没有目标(event.target),它的 event 对象不包含其它信息。
支持 readystatechange 事件的浏览器有:IE、Firefox4+ 和 Opera。
readystatechange 事件在与 load 事件一起使用时,无法预测两个事件触发的先后顺序。交互阶段可能早于或晚于完成阶段出现,无法确保顺序。在包含较多外部资源的页面中,交互阶段更有可能出现的早;在包含较少外部资源的页面中,完成阶段更有可能出现的早。因此,为了尽可能抢到先机,有必要同时检测交互和完成阶段,如:
EventUtil.addHandler(document, "readystatechange", function(event){
if (document.readyState == "interactive" || document.readyState == "complete") {
// 防止多次触发 readystatechange 事件
EventUtil.removeHandler(document, "readystatechange", arguments.callee);
alert("文档加载完毕");
}
});
这样使用 readystatechange 事件与使用 DOMContentLoaded 事件的效果十分相近了。
另外,可以用 readystatechange 事件来监听使用 JavaScript 动态加载的 JavaScript(IE 和 Opera 中) 和 CSS(IE 中) 文件是否已经加载完成(浏览器都有一个特点:只有把动态创建的元素添加到页面中,浏览器才会开始下载该外部资源)。readyState 属性无论等于 loaded 或 complete 都可以表示资源已经加载完成,有时候,readyState 属性会停在 loaded 阶段而永远不会“完成”;有时候,又会跳过 loaded 阶段直接“完成”。于是需要这样监听一下:
监听动态加载 JavaScript 是否完成:
EventUtil.addHandler(window, "load", function(){
var script = document.createElement("script");
EventUtil.addHandler(script, "readystatechange", function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
if (document.readyState == "loaded" || document.readyState == "complete") {
EventUtil.removeHandler(target, "readystatechange", arguments.callee);
alert("script 文件加载完毕");
}
});
script.src = "client.js";
document.body.appendChild(script);
});
监听动态加载 CSS 是否完成:
EventUtil.addHandler(window, "load", function(){
var link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
EventUtil.addHandler(link, "readystatechange", function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
if (document.readyState == "loaded" || document.readyState == "complete") {
EventUtil.removeHandler(target, "readystatechange", arguments.callee);
alert("CSS 文件加载完毕");
}
});
link.href = "public.css";
document.getElementsByTagName("head")[0].appendChild(link);
});
(5)、pageshow 和 pagehide 事件
Firefox 和 Opera 中有一个特性名叫“往返缓存”(back-forward cache 或 bfcache)。
bfcache 不仅保存着页面的数据,还保存着 DOM 和 JavaScript 的状态;实际上是将整个页面都保存在了内存里。
bfcache 可以在用户使用浏览器的“前进”和“后退”按钮时加快页面的转换速度——如果页面位于 bfcache 中,那么再次打开该页面时就不会触发load事件。
Firefox 为 bfcache 提供了两个新事件:pageshow 和 pagehide 事件。
- 这两个事件的目标(event.target)是 document,但是必须将其事件处理程序添加到 window 对象上。
- pageshow 和 pagehide 事件的区别:
- pageshow 事件:在第一次加载页面和重新加载页面的情况下,该事件会在 load 事件之后触发;而对于 bfcache 中的页面,该事件会在页面状态完全恢复的那一刻触发。
- pagehide 事件:该事件会在浏览器卸载页面的时候触发,而且是在 unload 事件之前触发的。
- 这两个事件的 event 对象除了包含通常的属性之外,还包含一个名为 persisted 的布尔值属性。
- pageshow 事件中:如果页面被保存在 bfcache 中,这个属性返回 true;否则,这个属性返回 false。所以,第一次触发 pageshow 事件时,persisted 值一定是 false。
- pagehide 事件中:如果页面卸载后被保存在 bfcache 中,这个属性返回 true;否则,这个属性返回 false。
- 支持这两个事件的浏览器有 Firefox、Safari5+、Chrome 和 Opera,IE9及其之前的版本不支持它们。
使用案例:
(function(){
var showCount = 0;
EventUtil.addHandler(window, "load", function(){
alert("页面加载完成");
});
EventUtil.addHandler(window, "pageshow", function(event){
showCount++;
console.log(`页面从后台第${showCount}次转为前台,persisted 的值是${event.persisted}`);
});
})();
EventUtil.addHandler(window, "pagehide", function(event){
alert(`页面是否已转为后台运行${event.persisted}`);
});
上述代码使用了私有作用域,避免了污染全局作用域。
注意:指定了 unload 事件处理程序的页面会被自动排除在 bfcache 之外,即使事件处理程序是空的,原因在于,unload 最常用于撤销在 load 中执行的操作,而跳过 load 后再次显示页面很可能会导致页面不正常。
(6)、hashchange 事件
hashchange 事件的作用:当 URL 的参数列表(及 URL 中“#”后面的所有字符串)发生变化时,通知开发人员。
必须要把 hashchange 事件添加给 window 对象。
hashchange 事件的 event 对象新增了两个属性:oldURL 和 newURL。这两属性分别保存着参数列表变化前后的完整 URL。
支持 hashchange 事件的浏览器有 IE8+、Firefox3.6+、Safari5+、Chrome 和 Opera10.6+。
支持 oldURL 和 newURL 属性的只有 Firefox6+、Chrome 和 Opera10.6+。因此,最好使用 location 对象来确定当前参数列表。
EventUtil.addHandler(window, "hashchange", function(event){
alert(`当前 hash 值${location.hash}`);
});
使用下面的方法可以跨浏览器检测浏览器是否支持 hashchange 事件:
var isSupported = ("onhashchange" in window) && (document.documentMode === undefined || document.documentMode > 7);
7、设备事件
(1)、orientationchange 事件
该事件用来确定用户何时将设备从横向查看模式切换到纵向查看模式。
该事件的 event 对象不包含任何信息。
该事件的 window.orientation 属性中包含 3 个值:0 表示肖像模式、90 表示左旋转的横向模式、-90 表示右旋转的横向模式。
该事件的使用典例:
EventUtil.addHandler(window, "load", function(event){
var div = document.getElementById("myDiv");
div.innerHTML = `1-当前 orientation 是:${window.orientation}`;
EventUtil.addHandler(window, "orientationchange", function(event){
div.innerHTML = `2-当前 orientation 是:${window.orientation}`;
});
});
这个例子中,当触发 load 事件时会显示最初的方向信息。添加了 orientationchange 事件处理程序后,只要发生这个事件,就会有表示新方向的信息更新在 div 里的值。
(2)、deviceorientationevent 事件
该事件是一个实验中的功能,支持该事件的浏览器有 Chrome、Firefox6+、Opera4.2+ 和 Safari;该事件在 IE 和 Opera 浏览器中没有实现。
该事件在 window 对象上触发。
该事件用来告知开发人员设备在空间中朝向哪里。设备在三维空间中是靠x、y、z轴来定位的:
事件对象包含 5 个属性:
- alpha:围绕 z 轴旋转时,y 轴的度数差;是一个介于 0~360 之间的浮点数。
- beta:围绕 x 轴旋转时,z 轴的度数差;是一个介于 -180~180 之间的浮点数。
- gamma:围绕y轴旋转时,z 轴的度数差;是一个介于 -90~90 之间的浮点数。
- absolute:布尔值,表示设备是否返回一个绝对值。
- compassCalibrated:布尔值,表示设备的指南针是否校准过。
该事件的使用典例:
EventUtil.addHandler(window, "deviceorientation", function(event){
var output = document.getElementById("output");
output.innerHTML = `alpha:${event.alpha}, beta:${event.beta}, gamma:${event.gamma}`;
});
EventUtil.addHandler(window, "deviceorientation", function(event){
var arrow = document.getElementById("arrow");
arrow.style.webkitTransform = `rotate(${Math.round(event.alpha)}deg)`;
});
(3)、devicemotion 事件
该事件是要告诉开发者设备什么时候移动,而不仅仅是设备方向如何改变。如通过该事件检测设备是否在往下掉,是否被走着的人拿在手里等等。
该事件的 event 对象包含以下属性:
- acceleration:包含 x、y 和 z 属性的对象,不考虑重力的情况下,返回每个方向的加速度。
- accelerationIncludingGravity:包含 x、y 和 z 属性的对象,在考虑 z 轴自然重力加速度的情况下,返回每个方向上的加速度。
- interval:毫秒表示的时间值,必须在另一个 devicemotion 事件触发前传入,是一个常量。
- rotationRate:包含表示方向的 alpha、beta 和 gamma 属性的对象。
以上属性,如果读取不到值,就会返回 null,所以应该先检测它们的值是否为 null。
该事件的使用典例:
EventUtil.addHandler(window, "devicemotion", function(event){
var output = document.getElementById("output");
if(event.rotationRate !== null){
output.innerHTML += `alpha:${event.rotationRate.alpha}, beta:${event.rotationRate.beta}, gamma:${event.rotationRate.gamma}`;
}
});
8、触摸与手势事件
(1)、兼容 DOM 的触摸事件
- touchstart 事件:触摸屏幕时触发。
- touchmove 事件:滑动时触发,调用 preventDefault() 方法可阻止滚动。
- touchend 事件:当手指从屏幕上移开时触发。
- touchcancel 事件:当系统停止跟踪触摸时触发。
上面的事件都会冒泡,都可以取消。每个触摸事件的 event 对象都提供了在鼠标事件中常见的属性。
(2)、跟踪触摸的事件
- touches 事件:表示当前跟踪的触摸操作的 Touch 对象的数组。
- targetTouches 事件:特定于事件目标的 Touch 对象的数组。
- changedTouches 事件:表示自上次触摸以来发生了什么改变的 Touch 对象的数组。
每个 Touch 对象包含的属性:
- clientX:视口中的 x 坐标。
- clientY:视口中的 y 坐标。
- pageX:触摸目标在页面中的 x 坐标。
- pageY:触摸目标在页面中的 y 坐标。
- screenX:触摸目标在屏幕中的 x 坐标。
- screenY:触摸目标在屏幕中的 y 坐标。
- identifier:标识触摸的唯一 ID。
- target:触摸 DOM 节点目标。
function handleTouchEvent () {
if (event.touches.length == 1) {
var output = document.getElementById("output");
switch (event.type) {
case "touchstart":
output.innerHTML = "Touch started (" + event.touches[0].clientX + "," + event.touches[0].clientY + ")";
break;
case "touchmove":
event.preventDefault();
output.innerHTML = "Touch moved (" + event.changedTouches[0].clientX + "," + event.changedTouches[0].clientY + ")";
break;
case "touchend":
output.innerHTML = "Touch ended (" + event.changedTouches[0].clientX + "," + event.changedTouches[0].clientY + ")";
break;
}
}
}
window.addEventListener("touchstart", handleTouchEvent);
window.addEventListener("touchmove", handleTouchEvent);
window.addEventListener("touchend", handleTouchEvent);
(3)、手势事件
- gesturestart 事件:当一个手指已经按在屏幕上而另一个手指又触摸屏幕时触发。
- gesturechange 事件:当触摸屏幕的任何一个手指的位置发生变化时触发。
- gestureend 事件:当任何一个手指从屏幕上面移开时触发。
其中event还包括除鼠标事件的属性之外另外两个属性:rotation和scale:其中rotation属性表示旋转的角度,从0开始,负值表示逆时针旋转,正值表示顺时针旋转;scale属性从1开始,随着距离拉大而增加,随着距离缩小而减小。
function handleGestureEvent() {
var output = document.getElementById("output");
switch (event.type) {
case "gesturestart":
output.innerHTML = "rotation: " + event.rotation + ";" + "scale: " + event.scale;
break;
case "gestureend":
output.innerHTML = "rotation: " + event.rotation + ";" + "scale: " + event.scale;
break;
case "gesturechange":
event.preventDefault();
output.innerHTML = "rotation: " + event.rotation + ";" + "scale: " + event.scale;
break;
}
}
document.addEventListener("gesturestart", handleGestureEvent);
document.addEventListener("gestureend", handleGestureEvent);
document.addEventListener("gesturechange", handleGestureEvent);
五、内存和性能
每当将事件处理程序指定给元素时,运行中的浏览器代码与支持页面交互的 JavaScript 代码之间就会建立一个链接。这种连接越多,页面执行的就越慢。我们可以采用:事件委托 或者 手动移除事件处理程序 来优化内存和性能。
1、事件委托(事件代理)
对“事件处理程序过多”问题的解决方案——事件委托——只制定一个事件处理程序,来管理某一类型的所有事件。
事件委托利用了事件冒泡。
例如:
<!DOCTYPE html>
<html>
<head>
<script src="./EventUtil.js"></script>
<title>事件委托</title>
</head>
<body>
<ul id="myList">
<li id="goSomewhere">go Somewhere</li>
<li id="doSomething">do Something</li>
<li id="sayHi">say Hi</li>
</ul>
<script>
var list = document.getElementById("myList");
var handler = function(e){
e = EventUtil.getEvent(e);
var target = EventUtil.getTarget();
switch (target.id) {
case 'goSomewhere':
location.href = "http://www.baidu.com";
break;
case 'doSomething':
document.title = "hello world";
break;
case 'sayHi':
alert("Hi");
break;
default:
break;
}
};
EventUtil.addHandler(list, "click", handler);
</script>
</body>
</html>
vue 中的事件委托:vue中的事件委托_weixin79893765432...的博客-CSDN博客
2、移除事件处理程序
在开发过程中或许会遇到这样的需求:有一个按钮包含在 <div> 元素中,单击这个按钮就将该按钮移除并替换成一条消息。
如果只是修改按钮父组件的 innerHTML 属性值,按钮是被移除了,但是这个按钮还带着一个事件处理程序呢。这时,不但 IE 浏览器可能会抛出错误,而且还会浪费内存,降低性能。怎么办呢?如果你知道某个元素即将被移除,那么最好手工移除其事件处理程序。
<div id="myDiv">
<input id="btn" type="button" value="提交">
</div>
<script>
var btn = document.getElementById("btn");
btn.onclick = function(){
btn.onclick = null;// 移除事件处理程序。
document.getElementById("myDiv").innerHTML = "审核中……"
}
</script>
六、事件防御
1、防抖和节流
js 函数防抖(debounce)与节流(throttle)