JavaScript Event对象
事件是一种异步编程的实现方式,本质上是程序各个组成部分之间传递的特定消息。DOM支持大量的事件,本节介绍DOM的事件编程。
EventTarget接口
DOM的事件操作(监听和触发),都定义在EventTarget接口。Element节点、document节点和window对象,都部署了这个接口。此外,XMLHttpRequest、AudioNode、AudioContext等浏览器内置对象,也部署了这个接口。
该接口就是三个方法,addEventListener和removeEventListener用于绑定和移除监听函数,dispatchEvent用于触发事件。
addEventListener()
addEventListener方法用于在当前节点或对象上,定义一个特定事件的监听函数。
target.addEventListener(type, listener[, useCapture]);
上面是使用格式,addEventListener方法接受三个参数。
-
type,事件名称,大小写不敏感。
-
listener,监听函数。指定事件发生时,会调用该监听函数。
- useCapture,监听函数是否在捕获阶段(capture)触发(参见后文《事件的传播》部分)。该参数是一个布尔值,默认为false(表示监听函数只在冒泡阶段被触发)。老式浏览器规定该参数必写,较新版本的浏览器允许该参数可选。为了保持兼容,建议总是写上该参数。
下面是一个例子。
function hello(){
console.log('Hello world');
}
var button = document.getElementById("btn");
button.addEventListener('click', hello, false);
上面代码中,addEventListener方法为button节点,绑定click事件的监听函数hello,该函数只在冒泡阶段触发。
可以使用addEventListener方法,为当前对象的同一个事件,添加多个监听函数。这些函数按照添加顺序触发,即先添加先触发。如果为同一个事件多次添加同一个监听函数,该函数只会执行一次,多余的添加将自动被去除(不必使用removeEventListener方法手动去除)。
function hello(){
console.log('Hello world');
}
document.addEventListener('click', hello, false);
document.addEventListener('click', hello, false);
执行上面代码,点击文档只会输出一行“Hello world”。
如果希望向监听函数传递参数,可以用匿名函数包装一下监听函数。
function print(x) {
console.log(x);
}
var el = document.getElementById("div1");
el.addEventListener("click", function(){print('Hello')}, false);
上面代码通过匿名函数,向监听函数print传递了一个参数。
removeEventListener()
removeEventListener方法用来移除addEventListener方法添加的事件监听函数。
div.addEventListener('click', listener, false);
div.removeEventListener('click', listener, false);
removeEventListener方法的参数,与addEventListener方法完全一致。它对第一个参数“事件类型”,也是大小写不敏感。
注意,removeEventListener方法移除的监听函数,必须与对应的addEventListener方法的参数完全一致,而且在同一个元素节点,否则无效。
dispatchEvent()
dispatchEvent方法在当前节点上触发指定事件,从而触发监听函数的执行。该方法返回一个布尔值,只要有一个监听函数调用了Event.preventDefault()
,则返回值为false,否则为true。
target.dispatchEvent(event)
dispatchEvent方法的参数是一个Event对象的实例。
para.addEventListener('click', hello, false);
var event = new Event('click');
para.dispatchEvent(event);
上面代码在当前节点触发了click事件。
如果dispatchEvent方法的参数为空,或者不是一个有效的事件对象,将报错。
下面代码根据dispatchEvent方法的返回值,判断事件是否被取消了。
var canceled = !cb.dispatchEvent(event);
if (canceled) {
console.log('事件取消');
} else {
console.log('事件未取消');
}
}
监听函数
监听函数(listener)是事件发生时,程序所要执行的函数。它是事件驱动编程模式的主要编程方式。
DOM提供三种方法,可以用来为事件绑定监听函数。
HTML标签的on-属性
HTML语言允许在元素标签的属性中,直接定义某些事件的监听代码。
<body onload="doSomething()">
<div onclick="console.log('触发事件')">
上面代码为body节点的load事件、div节点的click事件,指定了监听函数。
使用这个方法指定的监听函数,只会在冒泡阶段触发。
注意,使用这种方法时,on-属性的值是“监听代码”,而不是“监听函数”。也就是说,一旦指定事件发生,这些代码是原样传入JavaScript引擎执行。因此如果要执行函数,必须在函数名后面加上一对圆括号。
另外,Element节点的setAttribue方法,其实设置的也是这种效果。
el.setAttribute('onclick', 'doSomething()');
Element节点的事件属性
Element节点有事件属性,可以定义监听函数。
window.onload = doSomething;
div.onclick = function(event){
console.log('触发事件');
};
使用这个方法指定的监听函数,只会在冒泡阶段触发。
addEventListener方法
通过Element节点、document节点、window对象的addEventListener方法,也可以定义事件的监听函数。
window.addEventListener('load', doSomething, false);
addEventListener方法的详细介绍,参见本节EventTarget接口的部分。
在上面三种方法中,第一种“HTML标签的on-属性”,违反了HTML与JavaScript代码相分离的原则;第二种“Element节点的事件属性”的缺点是,同一个事件只能定义一个监听函数,也就是说,如果定义两次onclick属性,后一次定义会覆盖前一次。因此,这两种方法都不推荐使用,除非是为了程序的兼容问题,因为所有浏览器都支持这两种方法。
addEventListener是推荐的指定监听函数的方法。它有如下优点:
-
可以针对同一个事件,添加多个监听函数。
-
能够指定在哪个阶段(捕获阶段还是冒泡阶段)触发回监听函数。
- 除了DOM节点,还可以部署在window、XMLHttpRequest等对象上面,等于统一了整个JavaScript的监听函数接口。
this对象的指向
实际编程中,监听函数内部的this对象,常常需要指向触发事件的那个Element节点。
addEventListener方法指定的监听函数,内部的this对象总是指向触发事件的那个节点。
// HTML代码为
// <p id="para">Hello</p>
var id = 'doc';
var para = document.getElementById('para');
function hello(){
console.log(this.id);
}
para.addEventListener('click', hello, false);
执行上面代码,点击p节点会输出para。这是因为监听函数被“拷贝”成了节点的一个属性,使用下面的写法,会看得更清楚。
para.onclick = hello;
如果将监听函数部署在Element节点的on-属性上面,this不会指向触发事件的元素节点。
<p id="para" onclick="hello()">Hello</p>
<!-- 或者使用JavaScript代码 -->
<script>
pElement.setAttribute('onclick', 'hello()');
</script>
执行上面代码,点击p节点会输出doc。这是因为这里只是调用hello函数,而hello函数实际是在全局作用域执行,相当于下面的代码。
para.onclick = function(){
hello();
}
一种解决方法是,不引入函数作用域,直接在on-属性写入所要执行的代码。因为on-属性是在当前节点上执行的。
<p id="para" onclick="console.log(id)">Hello</p>
<!-- 或者 -->
<p id="para" onclick="console.log(this.id)">Hello</p>
上面两行,最后输出的都是para。
总结一下,以下写法的this对象都指向Element节点。
// JavaScript代码
element.onclick = print
element.addEventListener('click', print, false)
element.onclick = function () {console.log(this.id);}
// HTML代码
<element onclick="console.log(this.id)">
以下写法的this对象,都指向全局对象。
// JavaScript代码
element.onclick = function (){ doSomething() };
element.setAttribute('onclick', 'doSomething()');
// HTML代码
<element onclick="doSomething()">
事件的传播
传播的三个阶段
当一个事件发生以后,它会在不同的DOM节点之间传播(propagation)。这种传播分成三个阶段:
-
第一阶段:从window对象传导到目标节点,称为“捕获阶段”(capture phase)。
-
第二阶段:在目标节点上触发,称为“目标阶段”(target phase)。
- 第三阶段:从目标节点传导回window对象,称为“冒泡阶段”(bubbling phase)。
这种三阶段的传播模型,会使得一个事件在多个节点上触发。比如,假设div节点之中嵌套一个p节点。
<div>
<p>Click Me</p>
</div>
如果对这两个节点的click事件都设定监听函数,则click事件会被触发四次。
var phases = {
1: 'capture',
2: 'target',
3: 'bubble'
};
var div = document.querySelector('div');
var p = document.querySelector('p');
div.addEventListener('click', callback, true);
p.addEventListener('click', callback, true);
div.addEventListener('click', callback, false);
p.addEventListener('click', callback, false);
function callback(event) {
var tag = event.currentTarget.tagName;
var phase = phases[event.eventPhase];
console.log("Tag: '" + tag + "'. EventPhase: '" + phase + "'");
}
// 点击以后的结果
// Tag: 'DIV'. EventPhase: 'capture'
// Tag: 'P'. EventPhase: 'target'
// Tag: 'P'. EventPhase: 'target'
// Tag: 'DIV'. EventPhase: 'bubble'
上面代码表示,click事件被触发了四次:p节点的捕获阶段和冒泡阶段各1次,div节点的捕获阶段和冒泡阶段各1次。
- 捕获阶段:事件从div向p传播时,触发div的click事件;
- 目标阶段:事件从div到达p时,触发p的click事件;
- 目标阶段:事件离开p时,触发p的click事件;
- 冒泡阶段:事件从p传回div时,再次触发div的click事件。
注意,用户点击网页的时候,浏览器总是假定click事件的目标节点,就是点击位置的嵌套最深的那个节点(嵌套在div节点的p节点)。
事件传播的最上层对象是window,接着依次是document,html(document.documentElement)和body(document.dody)。也就是说,如果body元素中有一个div元素,点击该元素。事件的传播顺序,在捕获阶段依次为window、document、html、body、div,在冒泡阶段依次为div、body、html、document、window。
事件的代理
由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)。
var ul = document.querySelector('ul');
ul.addEventListener('click', function(event) {
if (event.target.tagName.toLowerCase() === 'li') {
// some code
}
});
上面代码的click事件的监听函数定义在ul节点,但是实际上,它处理的是子节点li的click事件。这样做的好处是,只要定义一个监听函数,就能处理多个子节点的事件,而且以后再添加子节点,监听函数依然有效。
如果希望事件到某个节点为止,不再传播,可以使用事件对象的stopPropagation方法。
p.addEventListener('click', function(event) {
event.stopPropagation();
});
使用上面的代码以后,click事件在冒泡阶段到达p节点以后,就不再向上(父节点的方向)传播了。
但是,stopPropagation方法不会阻止p节点上的其他click事件的监听函数。如果想要不再触发那些监听函数,可以使用stopImmediatePropagation方法。
p.addEventListener('click', function(event) {
event.stopImmediatePropagation();
});
p.addEventListener('click', function(event) {
// 不会被触发
});
Event对象
事件发生以后,会生成一个事件对象,作为参数传给监听函数。浏览器原生提供一个Event对象,所有的事件都是这个对象的实例,或者说继承了Event.prototype
对象。
Event对象本身就是一个构造函数,可以用来生成新的实例。
event = new Event(typeArg, eventInit);
Event构造函数接受两个参数。第一个参数是字符串,表示事件的名称;第二个参数是一个对象,表示事件对象的配置。该参数可以有以下两个属性。
-
bubbles:布尔值,可选,默认为false,表示事件对象是否冒泡。
- cancelable:布尔值,可选,默认为false,表示事件是否可以被取消。
var ev = new Event("look", {"bubbles":true, "cancelable":false});
document.dispatchEvent(ev);
上面代码新建一个look事件实例,然后使用dispatchEvent方法触发该事件。
IE8及以下版本,事件对象不作为参数传递,而是通过window对象的event属性读取,并且事件对象的target属性叫做srcElement属性。所以,以前获取事件信息,往往要写成下面这样。
function myEventHandler(event) {
var actualEvent = event || window.event;
var actualTarget = actualEvent.target || actualEvent.srcElement;
// ...
}
上面的代码只是为了说明以前的程序为什么这样写,在新代码中,这样的写法不应该再用了。
以下介绍Event实例的属性和方法。
bubbles,eventPhase
以下属性与事件的阶段有关。
(1)bubbles
bubbles属性返回一个布尔值,表示当前事件是否会冒泡。该属性为只读属性,只能在新建事件时改变。除非显式声明,Event构造函数生成的事件,默认是不冒泡的。
function goInput(e) {
if (!e.bubbles) {
passItOn(e);
} else {
doOutput(e);
}
}
上面代码根据事件是否冒泡,调用不同的函数。
(2)eventPhase
eventPhase属性返回一个整数值,表示事件目前所处的节点。
var phase = event.eventPhase;
- 0,事件目前没有发生。
- 1,事件目前处于捕获阶段,即处于从祖先节点向目标节点的传播过程中。该过程是从Window对象到Document节点,再到HTMLHtmlElement节点,直到目标节点的父节点为止。
- 2,事件到达目标节点,即target属性指向的那个节点。
- 3,事件处于冒泡阶段,即处于从目标节点向祖先节点的反向传播过程中。该过程是从父节点一直到Window对象。只有bubbles属性为true时,这个阶段才可能发生。
cancelable,defaultPrevented
以下属性与事件的默认行为有关。
(1)cancelable
cancelable属性返回一个布尔值,表示事件是否可以取消。该属性为只读属性,只能在新建事件时改变。除非显式声明,Event构造函数生成的事件,默认是不可以取消的。
var bool = event.cancelable;
如果要取消某个事件,需要在这个事件上面调用preventDefault方法,这会阻止浏览器对某种事件部署的默认行为。
(2)defaultPrevented
defaultPrevented属性返回一个布尔值,表示该事件是否调用过preventDefault方法。
if (e.defaultPrevented) {
// ...
}
currentTarget,target
以下属性与事件的目标节点有关。
(1)currentTarget
currentTarget属性返回事件当前所在的节点,即正在执行的监听函数所绑定的那个节点。作为比较,target属性返回事件发生的节点。如果监听函数在捕获阶段和冒泡阶段触发,那么这两个属性返回的值是不一样的。
function hide(e){
console.log(this === e.currentTarget); // true
e.currentTarget.style.visibility = "hidden";
}
para.addEventListener('click', hide, false);
上面代码中,点击para节点,该节点会不可见。另外,在监听函数中,currentTarget属性实际上等同于this对象。
(2)target
target属性返回触发事件的那个节点,即事件最初发生的节点。如果监听函数不在该节点触发,那么它与currentTarget属性返回的值是不一样的。
function hide(e){
console.log(this === e.target); // 有可能不是true
e.target.style.visibility = "hidden";
}
// HTML代码为
// <p id="para">Hello <em>World</em></p>
para.addEventListener('click', hide, false);
上面代码中,如果在para节点的em子节点上面点击,则e.target
指向em子节点,导致em子节点(即World部分)会不可见,且输出false。
在IE6—IE8之中,该属性的名字不是target,而是srcElement,因此经常可以看到下面这样的代码。
function hide(e) {
var target = e.target || e.srcElement;
target.style.visibility = 'hidden';
}
type,detail,timeStamp,isTrusted
以下属性与事件对象的其他信息相关。
(1)type
type属性返回一个字符串,表示事件类型,具体的值同addEventListener方法和removeEventListener方法的第一个参数一致,大小写不敏感。
var string = event.type;
(2)detail
detail属性返回一个数值,表示事件的某种信息。具体含义与事件类型有关,对于鼠标事件,表示鼠标按键在某个位置按下的次数,比如对于dblclick事件,detail属性的值总是2。
function giveDetails(e) {
this.textContent = e.detail;
}
el.onclick = giveDetails;
(3)timeStamp
timeStamp属性返回一个毫秒时间戳,表示事件发生的时间。
var number = event.timeStamp;
(4)isTrusted
isTrusted属性返回一个布尔值,表示该事件是否可以信任。
var bool = event.isTrusted;
Firefox浏览器中,用户触发的事件会返回true,脚本触发的事件返回false;IE浏览器中,除了使用createEvent方法生成的事件,所有其他事件都返回true;Chrome浏览器不支持该属性。
preventDefault()
preventDefault方法取消浏览器对当前事件的默认行为,比如点击链接后,浏览器跳转到指定页面,或者按一下空格键,页面向下滚动一段距离。该方法生效的前提是,事件的cancelable属性为true,如果为false,则调用该方法没有任何效果。
该方法不会阻止事件的进一步传播(stopPropagation方法可用于这个目的)。只要在事件的传播过程中(捕获阶段、目标阶段、冒泡阶段皆可),使用了preventDefault方法,该事件的默认方法就不会执行。
// HTML代码为
// <input type="checkbox" id="my-checkbox" />
var cb = document.getElementById('my-checkbox');
cb.addEventListener(
'click',
function (e){ e.preventDefault(); },
false
);
上面代码为点击单选框的事件,设置监听函数,取消默认行为。由于浏览器的默认行为是选中单选框,所以这段代码会导致无法选中单选框。
利用这个方法,可以为文本输入框设置校验条件。如果用户的输入不符合条件,就无法将字符输入文本框。
function checkName(e) {
if (e.charCode < 97 || e.charCode > 122) {
e.preventDefault();
}
}
上面函数设为文本框的keypress监听函数后,将只能输入小写字母,否则输入事件的默认事件(写入文本框)将被取消。
如果监听函数最后返回布尔值false(即return false),浏览器也不会触发默认行为,与preventDefault方法有等同效果。
stopPropagation()
stopPropagation方法阻止事件在DOM中继续传播,防止再触发定义在别的节点上的监听函数,但是不包括在当前节点上新定义的事件监听函数。
function stopEvent(e) {
e.stopPropagation();
}
el.addEventListener('click', stopEvent, false);
将上面函数指定为监听函数,会阻止事件进一步冒泡到el节点的父节点。
stopImmediatePropagation()
stopImmediatePropagation方法阻止同一个事件的其他监听函数被调用。
如果同一个节点对于同一个事件指定了多个监听函数,这些函数会根据添加的顺序依次调用。只要其中有一个监听函数调用了stopImmediatePropagation方法,其他的监听函数就不会再执行了。
function l1(e){
e.stopImmediatePropagation();
}
function l2(e){
console.log('hello world');
}
el.addEventListener('click', l1, false);
el.addEventListener('click', l2, false);
上面代码在el节点上,为click事件添加了两个监听函数l1和l2。由于l1调用了stopImmediatePropagation方法,所以l2不会被调用。
鼠标事件
事件种类
鼠标事件指与鼠标相关的事件,主要有以下一些。
(1)click事件
click事件当用户在Element节点、document节点、window对象上,单击鼠标(或者按下回车键)时触发。“鼠标单击”定义为,用户在同一个位置完成一次mousedown动作和mouseup动作。它们的触发顺序是:mousedown首先触发,mouseup接着触发,click最后触发。
下面是一个设置click事件监听函数的例子。
div.addEventListener("click", function( event ) {
// 显示在该节点,鼠标连续点击的次数
event.target.innerHTML = "click count: " + event.detail;
}, false);
下面的代码是利用click事件进行CSRF攻击(Cross-site request forgery)的一个例子。
<a href="http://www.harmless.com/" onclick="
var f = document.createElement('form');
f.style.display = 'none';
this.parentNode.appendChild(f);
f.method = 'POST';
f.action = 'http://www.example.com/account/destroy';
f.submit();
return false;">伪装的链接</a>
(2)dblclick事件
dblclick事件当用户在element、document、window对象上,双击鼠标时触发。该事件会在mousedown、mouseup、click之后触发。
(3)mouseup事件,mousedown事件
mouseup事件在释放按下的鼠标键时触发。
mousedown事件在按下鼠标键时触发。
(4)mousemove事件
mousemove事件当鼠标在一个节点内部移动时触发。当鼠标持续移动时,该事件会连续触发。为了避免性能问题,建议对该事件的监听函数做一些限定,比如限定一段时间内只能运行一次代码。
(5)mouseover事件,mouseenter事件
mouseover事件和mouseenter事件,都是鼠标进入一个节点时触发。
两者的区别是,mouseover事件会冒泡,mouseenter事件不会。子节点的mouseover事件会冒泡到父节点,进而触发父节点的mouseover事件。mouseenter事件就没有这种效果,所以进入子节点时,不会触发父节点的监听函数。
下面的例子是mouseenter事件与mouseover事件的区别。
// HTML代码为
// <ul id="test">
// <li>item 1</li>
// <li>item 2</li>
// <li>item 3</li>
// </ul>
var test = document.getElementById("test");
// 进入test节点以后,该事件只会触发一次
test.addEventListener("mouseenter", function( event ) {
event.target.style.color = "purple";
setTimeout(function() {
event.target.style.color = "";
}, 500);
}, false);
// 接入test节点以后,只要在子Element节点上移动,该事件会触发多次
test.addEventListener("mouseover", function( event ) {
event.target.style.color = "orange";
setTimeout(function() {
event.target.style.color = "";
}, 500);
}, false);
上面代码中,由于mouseover事件会冒泡,所以子节点的mouseover事件会触发父节点的监听函数。
(6)mouseout事件,mouseleave事件
mouseout事件和mouseleave事件,都是鼠标离开一个节点时触发。
两者的区别是,mouseout事件会冒泡,mouseleave事件不会。子节点的mouseout事件会冒泡到父节点,进而触发父节点的mouseout事件。mouseleave事件就没有这种效果,所以离开子节点时,不会触发父节点的监听函数。
(7)contextmenu
contextmenu事件在一个节点上点击鼠标右键时触发,或者按下“上下文菜单”键时触发。
MouseEvent对象
鼠标事件使用MouseEvent对象表示,它继承UIEvent对象和Event对象。浏览器提供一个MouseEvent构造函数,用于新建一个MouseEvent实例。
event = new MouseEvent(typeArg, mouseEventInit);
MouseEvent构造函数的第一个参数是事件名称(可能的值包括click、mousedown、mouseup、mouseover、mousemove、mouseout),第二个参数是一个事件初始化对象。该对象可以配置以下属性。
- screenX,设置鼠标相对于屏幕的水平坐标(但不会移动鼠标),默认为0,等同于MouseEvent.screenX属性。
- screenY,设置鼠标相对于屏幕的垂直坐标,默认为0,等同于MouseEvent.screenY属性。
- clientX,设置鼠标相对于窗口的水平坐标,默认为0,等同于MouseEvent.clientX属性。
- clientY,设置鼠标相对于窗口的垂直坐标,默认为0,等同于MouseEvent.clientY属性。
- ctrlKey,设置是否按下ctrl键,默认为false,等同于MouseEvent.ctrlKey属性。
- shiftKey,设置是否按下shift键,默认为false,等同于MouseEvent.shiftKey属性。
- altKey,设置是否按下alt键,默认为false,等同于MouseEvent.altKey属性。
- metaKey,设置是否按下meta键,默认为false,等同于MouseEvent.metaKey属性。
- button,设置按下了哪一个鼠标按键,默认为0。-1表示没有按键,0表示按下主键(通常是左键),1表示按下辅助键(通常是中间的键),2表示按下次要键(通常是右键)。
- buttons,设置按下了鼠标哪些键,是一个3个比特位的二进制值,默认为0。1表示按下主键(通常是左键),2表示按下次要键(通常是右键),4表示按下辅助键(通常是中间的键)。
- relatedTarget,设置一个Element节点,在mouseenter和mouseover事件时,表示鼠标刚刚离开的那个Element节点,在mouseout和mouseleave事件时,表示鼠标正在进入的那个Element节点。默认为null,等同于MouseEvent.relatedTarget属性。
以下属性也是可配置的,都继承自UIEvent构造函数和Event构造函数。
- bubbles,布尔值,设置事件是否冒泡,默认为false,等同于Event.bubbles属性。
- cancelable,布尔值,设置事件是否可取消,默认为false,等同于Event.cancelable属性。
- view,设置事件的视图,一般是window或document.defaultView,等同于Event.view属性。
- detail,设置鼠标点击的次数,等同于Event.detail属性。
下面是一个例子。
function simulateClick() {
var event = new MouseEvent('click', {
'bubbles': true,
'cancelable': true
});
var cb = document.getElementById('checkbox');
cb.dispatchEvent(event);
}
上面代码生成一个鼠标点击事件,并触发该事件。
以下介绍MouseEvent实例的属性。
altKey,ctrlKey,metaKey,shiftKey
以下属性返回一个布尔值,表示鼠标事件发生时,是否按下某个键。
- altKey属性:alt键
- ctrlKey属性:key键
- metaKey属性:Meta键(Mac键盘是一个四瓣的小花,Windows键盘是Windows键)
- shiftKey属性:Shift键
// HTML代码为
// <body onclick="showkey(event);">
function showKey(e){
console.log("ALT key pressed: " + e.altKey);
console.log("CTRL key pressed: " + e.ctrlKey);
console.log("META key pressed: " + e.metaKey);
console.log("META key pressed: " + e.shiftKey);
}
上面代码中,点击网页会输出是否同时按下Alt键。
button,buttons
以下属性返回事件的鼠标键信息。
(1)button
button属性返回一个数值,表示按下了鼠标哪个键。
- -1:没有按下键。
- 0:按下主键(通常是左键)。
- 1:按下辅助键(通常是中键或者滚轮键)。
- 2:按下次键(通常是右键)。
// HTML代码为
// <button onmouseup="whichButton(event);">点击</button>
var whichButton = function (e) {
switch (e.button) {
case 0:
console.log('Left button clicked.');
break;
case 1:
console.log('Middle button clicked.');
break;
case 2:
console.log('Right button clicked.');
break;
default:
console.log('Unexpected code: ' + e.button);
}
}
(2)buttons
buttons属性返回一个3个比特位的值,表示同时按下了哪些键。它用来处理同时按下多个鼠标键的情况。
- 1:二进制为001,表示按下左键。
- 2:二进制为010,表示按下右键。
- 4:二进制为100,表示按下中键或滚轮键。
同时按下多个键的时候,每个按下的键对应的比特位都会有值。比如,同时按下左键和右键,会返回3(二进制为011)。
clientX,clientY,movementX,movementY,screenX,screenY
以下属性与事件的位置相关。
(1)clientX,clientY
clientX属性返回鼠标位置相对于浏览器窗口左上角的水平坐标,单位为像素,与页面是否横向滚动无关。
clientY属性返回鼠标位置相对于浏览器窗口左上角的垂直坐标,单位为像素,与页面是否纵向滚动无关。
// HTML代码为
// <body onmousedown="showCoords(event)">
function showCoords(evt){
console.log(
"clientX value: " + evt.clientX + "\n" +
"clientY value: " + evt.clientY + "\n"
);
}
(2)movementX,movementY
movementX属性返回一个水平位移,单位为像素,表示当前位置与上一个mousemove事件之间的水平距离。在数值上,等于currentEvent.movementX = currentEvent.screenX - previousEvent.screenX。
movementY属性返回一个垂直位移,单位为像素,表示当前位置与上一个mousemove事件之间的垂直距离。在数值上,等于currentEvent.movementY = currentEvent.screenY - previousEvent.screenY。
(3)screenX,screenY
screenX属性返回鼠标位置相对于屏幕左上角的水平坐标,单位为像素。
screenY属性返回鼠标位置相对于屏幕左上角的垂直坐标,单位为像素。
// HTML代码为
// <body onmousedown="showCoords(event)">
function showCoords(evt){
console.log(
"screenX value: " + evt.screenX + "\n"
+ "screenY value: " + evt.screenY + "\n"
);
}
relatedTarget
relatedTarget属性返回事件的次要相关节点。对于那些没有次要相关节点的事件,该属性返回null。
下表列出不同事件的target属性和relatedTarget属性含义。
事件名称 | target属性 | relatedTarget属性 |
---|---|---|
focusin | 接受焦点的节点 | 丧失焦点的节点 |
focusout | 丧失焦点的节点 | 接受焦点的节点 |
mouseenter | 将要进入的节点 | 将要离开的节点 |
mouseleave | 将要离开的节点 | 将要进入的节点 |
mouseout | 将要离开的节点 | 将要进入的节点 |
mouseover | 将要进入的节点 | 将要离开的节点 |
dragenter | 将要进入的节点 | 将要离开的节点 |
dragexit | 将要离开的节点 | 将要进入的节点 |
下面是一个例子。
// HTML代码为
// <div id="outer" style="height:50px;width:50px;border-width:1px solid black;">
// <div id="inner" style="height:25px;width:25px;border:1px solid black;"></div>
// </div>
var inner = document.getElementById("inner");
inner.addEventListener("mouseover", function (){
console.log('进入' + event.target.id + " 离开" + event.relatedTarget.id);
});
inner.addEventListener("mouseenter", function (){
console.log('进入' + event.target.id + " 离开" + event.relatedTarget.id);
});
inner.addEventListener("mouseout", function (){
console.log('离开' + event.target.id + " 进入" + event.relatedTarget.id);
});
inner.addEventListener("mouseleave", function (){
console.log('离开' + event.target.id + " 进入" + event.relatedTarget.id);
});
// 鼠标从outer进入inner,输出
// 进入inner 离开outer
// 进入inner 离开outer
// 鼠标从inner进入outer,输出
// 离开inner 进入outer
// 离开inner 进入outer
wheel事件
wheel事件是与鼠标滚轮相关的事件,目前只有一个wheel事件。用户滚动鼠标的滚轮,就触发这个事件。
该事件除了继承了MouseEvent、UIEvent、Event的属性,还有几个自己的属性。
- deltaX:返回一个数值,表示滚轮的水平滚动量。
- deltaY:返回一个数值,表示滚轮的垂直滚动量。
- deltaZ:返回一个数值,表示滚轮的Z轴滚动量。
- deltaMode:返回一个数值,表示滚动的单位,适用于上面三个属性。0表示像素,1表示行,2表示页。
浏览器提供一个WheelEvent构造函数,可以用来生成滚轮事件的实例。它接受两个参数,第一个是事件名称,第二个是配置对象。
var syntheticEvent = new WheelEvent("syntheticWheel", {"deltaX": 4, "deltaMode": 0});
键盘事件
键盘事件用来描述键盘行为,主要有keydown、keypress、keyup三个事件。
-
keydown:按下键盘时触发该事件。
-
keypress:只要按下的键并非Ctrl、Alt、Shift和Meta,就接着触发keypress事件。
- keyup:松开键盘时触发该事件。
下面是一个例子,对文本框设置keypress监听函数,只允许输入数字。
// HTML代码为
// <input type="text"
// name="myInput"
// onkeypress="return numbersOnly(this, event);"
// onpaste="return false;"
// />
function numbersOnly(oToCheckField, oKeyEvent) {
return oKeyEvent.charCode === 0
|| /\d/.test(String.fromCharCode(oKeyEvent.charCode));
}
如果用户一直按键不松开,就会连续触发键盘事件,触发的顺序如下。
- keydown
- keypress
- keydown
- keypress
- (重复以上过程)
- keyup
键盘事件使用KeyboardEvent对象表示,该对象继承了UIEvent和MouseEvent对象。浏览器提供KeyboardEvent构造函数,用来新建键盘事件的实例。
event = new KeyboardEvent(typeArg, KeyboardEventInit);
KeyboardEvent构造函数的第一个参数是一个字符串,表示事件类型,第二个参数是一个事件配置对象,可配置以下字段。
- key,对应KeyboardEvent.key属性,默认为空字符串。
- ctrlKey,对应KeyboardEvent.ctrlKey属性,默认为false。
- shiftKey,对应KeyboardEvent.shiftKey属性,默认为false。
- altKey,对应KeyboardEvent.altKey属性,默认为false。
- metaKey,对应KeyboardEvent.metaKey属性,默认为false。
下面就是KeyboardEvent实例的属性介绍。
altKey,ctrlKey,metaKey,shiftKey
以下属性返回一个布尔值,表示是否按下对应的键。
- altKey:alt键
- ctrlKey:ctrl键
- metaKey:meta键(mac系统是一个四瓣的小花,windows系统是windows键)
- shiftKey:shift键
function showChar(e){
console.log("ALT: " + e.altKey);
console.log("CTRL: " + e.ctrlKey);
console.log("Meta: " + e.metaKey);
console.log("Meta: " + e.shiftKey);
}
key,charCode
key属性返回一个字符串,表示按下的键名。如果同时按下一个控制键和一个符号键,则返回符号键的键名。比如,按下Ctrl+a,则返回a。如果无法识别键名,则返回字符串Unidentified。
主要功能键的键名(不同的浏览器可能有差异):Backspace,Tab,Enter,Shift,Control,Alt,CapsLock,CapsLock,Esc,Spacebar,PageUp,PageDown,End,Home,Left,Right,Up,Down,PrintScreen,Insert,Del,Win,F1~F12,NumLock,Scroll等。
charCode属性返回一个数值,表示keypress事件按键的Unicode值,keydown和keyup事件不提供这个属性。注意,该属性已经从标准移除,虽然浏览器还支持,但应该尽量不使用。
进度事件
进度事件用来描述一个事件进展的过程,比如XMLHttpRequest对象发出的HTTP请求的过程、、、、、加载外部资源的过程。下载和上传都会发生进度事件。
进度事件有以下几种。
-
abort事件:当进度事件被中止时触发。如果发生错误,导致进程中止,不会触发该事件。
-
error事件:由于错误导致资源无法加载时触发。
-
load事件:进度成功结束时触发。
-
loadstart事件:进度开始时触发。
-
loadend事件:进度停止时触发,发生顺序排在error事件\abort事件\load事件后面。
-
progress事件:当操作处于进度之中,由传输的数据块不断触发。
- timeout事件:进度超过限时触发。
image.addEventListener('load', function(event) {
image.classList.add('finished');
});
image.addEventListener('error', function(event) {
image.style.display = 'none';
});
上面代码在图片元素加载完成后,为图片元素的class属性添加一个值“finished”。如果加载失败,就把图片元素的样式设置为不显示。
有时候,图片加载会在脚本运行之前就完成,尤其是当脚本放置在网页底部的时候,因此有可能使得load和error事件的监听函数根本不会被执行。所以,比较可靠的方式,是用complete属性先判断一下是否加载完成。
function loaded() {
// code after image loaded
}
if (image.complete) {
loaded();
} else {
image.addEventListener('load', loaded);
}
由于DOM没有提供像complete属性那样的,判断是否发生加载错误的属性,所以error事件的监听函数最好放在img元素的HTML属性中,这样才能保证发生加载错误时百分之百会执行。
<img src="https://atts.w3cschool.cn/attachments/image/cimg/url" onerror="this.style.display='none';" />
error事件有一个特殊的性质,就是不会冒泡。这样的设计是正确的,防止引发父元素的error事件监听函数。
进度事件使用ProgressEvent对象表示。ProgressEvent实例有以下属性。
-
lengthComputable:返回一个布尔值,表示当前进度是否具有可计算的长度。如果为false,就表示当前进度无法测量。
-
total:返回一个数值,表示当前进度的总长度。如果是通过HTTP下载某个资源,表示内容本身的长度,不含HTTP头部的长度。如果lengthComputable属性为false,则total属性就无法取得正确的值。
- loaded:返回一个数值,表示当前进度已经完成的数量。该属性除以total属性,就可以得到目前进度的百分比。
下面是一个例子。
var xhr = new XMLHttpRequest();
xhr.addEventListener("progress", updateProgress, false);
xhr.addEventListener("load", transferComplete, false);
xhr.addEventListener("error", transferFailed, fals