事件 上
约 11812 字大约 39 分钟
JavaScript
2025-05-15
EventTarget 接口
事件的本质是程序各个组成部分之间的一种通信方式,也是异步编程的一种实现。
DOM 支持大量的事件,本章开始介绍 DOM 的事件编程。
介绍具体的事件之前,先来看看如何让 DOM 节点监听事件。
概述
DOM 节点的事件操作(监听和触发),都定义在EventTarget接口。所有节点对象都部署了这个接口,其他一些需要事件通信的浏览器内置对象(比如,XMLHttpRequest、AudioNode、AudioContext)也部署了这个接口。
EventTarget.addEventListener()
EventTarget.addEventListener()用于在当前节点或对象上(即部署了 EventTarget 接口的对象),定义一个特定事件的监听函数。
一旦这个事件发生,就会执行监听函数。该方法没有返回值。
target.addEventListener(type, listener[, useCapture]);该方法接受三个参数。
type:事件名称,大小写敏感。listener:监听函数。事件发生时,会调用该监听函数。useCapture:布尔值,如果设为true,表示监听函数将在捕获阶段(capture)触发。该参数可选,默认值为false(监听函数只在冒泡阶段被触发)。
function hello() {
console.log('Hello world');
}
var button = document.getElementById('btn');
button.addEventListener('click', hello, false);上面代码中,button节点的addEventListener()方法绑定click事件的监听函数hello(),该函数只在冒泡阶段触发。
关于参数,有两个地方需要注意。
首先,第二个参数除了监听函数,还可以是一个具有handleEvent方法的对象,效果与监听函数一样。
buttonElement.addEventListener('click', {
handleEvent: function (event) {
console.log('click');
}
});第三个参数除了布尔值useCapture,还可以是一个监听器配置对象,定制事件监听行为。该对象有以下属性。
capture:布尔值,如果设为true,表示监听函数在捕获阶段触发,默认为false,在冒泡阶段触发。once:布尔值,如果设为true,表示监听函数执行一次就会自动移除,后面将不再监听该事件。该属性默认值为false。passive:布尔值,设为true时,表示禁止监听函数调用preventDefault()方法。如果调用了,浏览器将忽略这个要求,并在控制台输出一条警告。该属性默认值为false。signal:该属性的值为一个 AbortSignal 对象,为监听器设置了一个信号通道,用来在需要时发出信号,移除监听函数。
addEventListener()方法可以为针对当前对象的同一个事件,添加多个不同的监听函数。这些函数按照添加顺序触发,即先添加先触发。如果为同一个事件多次添加同一个监听函数,该函数只会执行一次,多余的添加将自动被去除(不必使用removeEventListener()方法手动去除)。
如果希望向监听函数传递参数,可以用匿名函数包装一下监听函数。
function print(x) {
console.log(x);
}
var el = document.getElementById('div1');
el.addEventListener('click', function () { print('Hello'); }, false);上面代码通过匿名函数,向监听函数print传递了一个参数。
监听函数内部的this,指向当前事件所在的那个对象。
// HTML 代码如下
// <p id="para">Hello</p>
var para = document.getElementById('para');
para.addEventListener('click', function (e) {
console.log(this.nodeName); // "P"
}, false);EventTarget.removeEventListener()
EventTarget.removeEventListener()方法用来移除addEventListener()方法添加的事件监听函数。该方法没有返回值。
removeEventListener()方法的参数,与addEventListener()方法完全一致。它的第一个参数“事件类型”,大小写敏感。
注意,removeEventListener()方法移除的监听函数,必须是addEventListener()方法添加的那个监听函数,而且必须在同一个元素节点,否则无效。
div.addEventListener('click', function (e) {}, false);
div.removeEventListener('click', function (e) {}, false);上面代码中,removeEventListener()方法无效,因为监听函数不是同一个匿名函数。
element.addEventListener('mousedown', handleMouseDown, true);
element.removeEventListener("mousedown", handleMouseDown, false);上面代码中,removeEventListener()方法也是无效的,因为第三个参数不一样。
EventTarget.dispatchEvent()
EventTarget.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)对事件做出反应。事件发生后,浏览器监听到了这个事件,就会执行对应的监听函数。这是事件驱动编程模式(event-driven)的主要编程方式。
JavaScript 有三种方法,可以为事件绑定监听函数。
HTML 的 on- 属性
HTML 语言允许在元素的属性中,直接定义某些事件的监听代码。
<body onload="doSomething()">
<div onclick="console.log('触发事件')">上面代码为body节点的load事件、div节点的click事件,指定了监听代码。一旦事件发生,就会执行这段代码。
元素的事件监听属性,都是on加上事件名,比如onload就是on + load,表示load事件的监听代码。
注意,这些属性的值是将会执行的代码,而不是一个函数。
<!-- 正确 -->
<body onload="doSomething()">
<!-- 错误 -->
<body onload="doSomething">一旦指定的事件发生,on-属性的值是原样传入 JavaScript 引擎执行。因此如果要执行函数,不要忘记加上一对圆括号。
使用这个方法指定的监听代码,只会在冒泡阶段触发。
<div onclick="console.log(2)">
<button onclick="console.log(1)">点击</button>
</div>上面代码中,<button>是<div>的子元素。<button>的click事件,也会触发<div>的click事件。
由于on-属性的监听代码,只在冒泡阶段触发,所以点击结果是先输出1,再输出2,即事件从子元素开始冒泡到父元素。
直接设置on-属性,与通过元素节点的setAttribute方法设置on-属性,效果是一样的。
el.setAttribute('onclick', 'doSomething()');
// 等同于
// <Element onclick="doSomething()">元素节点的事件属性
元素节点对象的事件属性,同样可以指定监听函数。
window.onload = doSomething;
div.onclick = function (event) {
console.log('触发事件');
};使用这个方法指定的监听函数,也是只会在冒泡阶段触发。
注意,这种方法与 HTML 的on-属性的差异是,它的值是函数名(doSomething),而不像后者,必须给出完整的监听代码(doSomething())。
EventTarget.addEventListener()
所有 DOM 节点实例都有addEventListener方法,用来为该节点定义事件的监听函数。
window.addEventListener('load', doSomething, false);小结
上面三种方法,第一种“HTML 的 on- 属性”,违反了 HTML 与 JavaScript 代码相分离的原则,将两者写在一起,不利于代码分工,因此不推荐使用。
第二种“元素节点的事件属性”的缺点在于,同一个事件只能定义一个监听函数,也就是说,如果定义两次onclick属性,后一次定义会覆盖前一次。因此,也不推荐使用。
第三种EventTarget.addEventListener是推荐的指定监听函数的方法。它有如下优点:
- 同一个事件可以添加多个监听函数。
- 能够指定在哪个阶段(捕获阶段还是冒泡阶段)触发监听函数。
- 除了 DOM 节点,其他对象(比如
window、XMLHttpRequest等)也有这个接口,它等于是整个 JavaScript 统一的监听函数接口。
this 的指向
监听函数内部的this指向触发事件的那个元素节点。
<button id="btn" onclick="console.log(this.id)">点击</button>执行上面代码,点击后会输出btn。
其他两种监听函数的写法,this的指向也是如此。
事件的传播
一个事件发生后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段。
- 第一阶段:从
window对象传导到目标节点(上层传到底层),称为“捕获阶段”(capture phase)。 - 第二阶段:在目标节点上触发,称为“目标阶段”(target phase)。
- 第三阶段:从目标节点传导回
window对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)。
这种三阶段的传播模型,使得同一个事件会在多个节点上触发。
<div>
<p>点击</p>
</div>上面代码中,<div>节点之中有一个<p>节点。
如果对这两个节点,都设置click事件的监听函数(每个节点的捕获阶段和冒泡阶段,各设置一个监听函数),共计设置四个监听函数。然后,对<p>点击,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事件被触发了四次:<div>节点的捕获阶段和冒泡阶段各1次,<p>节点的目标阶段触发了2次。
- 捕获阶段:事件从
<div>向<p>传播时,触发<div>的click事件; - 目标阶段:事件从
<div>到达<p>时,触发<p>的click事件; - 冒泡阶段:事件从
<p>传回<div>时,再次触发<div>的click事件。
其中,<p>节点有两个监听函数(addEventListener方法第三个参数的不同,会导致绑定两个监听函数),因此它们都会因为click事件触发一次。所以,<p>会在target阶段有两次输出。
注意,浏览器总是假定click事件的目标节点,就是点击位置嵌套最深的那个节点(本例是<div>节点里面的<p>节点)。所以,<p>节点的捕获阶段和冒泡阶段,都会显示为target阶段。
事件传播的最上层对象是window,接着依次是document,html(document.documentElement)和body(document.body)。也就是说,上例的事件传播顺序,在捕获阶段依次为window、document、html、body、div、p,在冒泡阶段依次为p、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事件。
这样做的好处是,只要定义一个监听函数,就能处理多个子节点的事件,而不用在每个<li>节点上定义监听函数。而且以后再添加子节点,监听函数依然有效。
如果希望事件到某个节点为止,不再传播,可以使用事件对象的stopPropagation方法。
// 事件传播到 p 元素后,就不再向下传播了
p.addEventListener('click', function (event) {
event.stopPropagation();
}, true);
// 事件冒泡到 p 元素后,就不再向上冒泡了
p.addEventListener('click', function (event) {
event.stopPropagation();
}, false);上面代码中,stopPropagation方法分别在捕获阶段和冒泡阶段,阻止了事件的传播。
但是,stopPropagation方法只会阻止事件的传播,不会阻止该事件触发<p>节点的其他click事件的监听函数。也就是说,不是彻底取消click事件。
p.addEventListener('click', function (event) {
event.stopPropagation();
console.log(1);
});
p.addEventListener('click', function(event) {
// 会触发
console.log(2);
});上面代码中,p元素绑定了两个click事件的监听函数。stopPropagation方法只能阻止这个事件的传播,不能取消这个事件,因此,第二个监听函数会触发。输出结果会先是1,然后是2。
如果想要彻底取消该事件,不再触发后面所有click的监听函数,可以使用stopImmediatePropagation方法。
p.addEventListener('click', function (event) {
event.stopImmediatePropagation();
console.log(1);
});
p.addEventListener('click', function(event) {
// 不会被触发
console.log(2);
});上面代码中,stopImmediatePropagation方法可以彻底取消这个事件,使得后面绑定的所有click监听函数都不再触发。所以,只会输出1,不会输出2。
Event 对象
概述
事件发生以后,会产生一个事件对象,作为参数传给监听函数。
浏览器原生提供一个Event对象,所有的事件都是这个对象的实例,或者说继承了Event.prototype对象。
Event对象本身就是一个构造函数,可以用来生成新的实例。
event = new Event(type, options);Event构造函数接受两个参数。第一个参数type是字符串,表示事件的名称;第二个参数options是一个对象,表示事件对象的配置。该对象主要有下面两个属性。
bubbles:布尔值,可选,默认为false,表示事件对象是否冒泡。cancelable:布尔值,可选,默认为false,表示事件是否可以被取消,即能否用Event.preventDefault()取消这个事件。一旦事件被取消,就好像从来没有发生过,不会触发浏览器对该事件的默认行为。
var ev = new Event(
'look',
{
'bubbles': true,
'cancelable': false
}
);
document.dispatchEvent(ev);上面代码新建一个look事件实例,然后使用dispatchEvent方法触发该事件。
注意,如果不是显式指定bubbles属性为true,生成的事件就只能在“捕获阶段”触发监听函数。
// HTML 代码为
// <div><p>Hello</p></div>
var div = document.querySelector('div');
var p = document.querySelector('p');
function callback(event) {
var tag = event.currentTarget.tagName;
console.log('Tag: ' + tag); // 没有任何输出
}
div.addEventListener('click', callback, false);
var click = new Event('click');
p.dispatchEvent(click);上面代码中,p元素发出一个click事件,该事件默认不会冒泡。div.addEventListener方法指定在冒泡阶段监听,因此监听函数不会触发。如果写成div.addEventListener('click', callback, true),那么在“捕获阶段”可以监听到这个事件。
实例属性
Event.bubbles
Event.bubbles属性返回一个布尔值,表示当前事件是否会冒泡。该属性为只读属性,一般用来了解 Event 实例是否可以冒泡。前面说过,除非显式声明,Event构造函数生成的事件,默认是不冒泡的。
Event.eventPhase
Event.eventPhase属性返回一个整数常量,表示事件目前所处的阶段。该属性只读。
var phase = event.eventPhase;Event.eventPhase的返回值有四种可能。
- 0,事件目前没有发生。
- 1,事件目前处于捕获阶段,即处于从祖先节点向目标节点的传播过程中。
- 2,事件到达目标节点,即
Event.target属性指向的那个节点。 - 3,事件处于冒泡阶段,即处于从目标节点向祖先节点的反向传播过程中。
Event.cancelable
Event.cancelable属性返回一个布尔值,表示事件是否可以取消。该属性为只读属性,一般用来了解 Event 实例的特性。
大多数浏览器的原生事件是可以取消的。比如,取消click事件,点击链接将无效。但是除非显式声明,Event构造函数生成的事件,默认是不可以取消的。
var evt = new Event('foo');
evt.cancelable // false当Event.cancelable属性为true时,调用Event.preventDefault()就可以取消这个事件,阻止浏览器对该事件的默认行为。
如果事件不能取消,调用Event.preventDefault()会没有任何效果。所以使用这个方法之前,最好用Event.cancelable属性判断一下是否可以取消。
Event.cancelBubble
Event.cancelBubble属性是一个布尔值,如果设为true,相当于执行Event.stopPropagation(),可以阻止事件的传播。
event.defaultPrevented
Event.defaultPrevented属性返回一个布尔值,表示该事件是否调用过Event.preventDefault方法。该属性只读。
if (event.defaultPrevented) {
console.log('该事件已经取消了');
}Event.currentTarget
Event.target
事件发生以后,会经过捕获和冒泡两个阶段,依次通过多个 DOM 节点。因此,任意事件都有两个与事件相关的节点,一个是事件的原始触发节点(Event.target),另一个是事件当前正在通过的节点(Event.currentTarget)。前者通常是后者的后代节点。
Event.currentTarget属性返回事件当前所在的节点,即事件当前正在通过的节点,也就是当前正在执行的监听函数所在的那个节点。随着事件的传播,这个属性的值会变。
Event.target属性返回原始触发事件的那个节点,即事件最初发生的节点。这个属性不会随着事件的传播而改变。
事件传播过程中,不同节点的监听函数内部的Event.target与Event.currentTarget属性的值是不一样的。
// HTML 代码为
// <p id="para">Hello <em>World</em></p>
function hide(e) {
// 不管点击 Hello 或 World,总是返回 true
console.log(this === e.currentTarget);
// 点击 Hello,返回 true
// 点击 World,返回 false
console.log(this === e.target);
}
document.getElementById('para').addEventListener('click', hide, false);e.target总是指向原始点击位置的那个节点。
Event.type
Event.type属性返回一个字符串,表示事件类型。事件的类型是在生成事件的时候指定的。该属性只读。
var evt = new Event('foo');
evt.type // "foo"Event.timeStamp
Event.timeStamp属性返回一个毫秒时间戳,表示事件发生的时间。它是相对于网页加载成功开始计算的。
var evt = new Event('foo');
evt.timeStamp // 3683.6999999995896Event.isTrusted
Event.isTrusted属性返回一个布尔值,表示该事件是否由真实的用户行为产生。比如,用户点击链接会产生一个click事件,该事件是用户产生的;Event构造函数生成的事件,则是脚本产生的。
var evt = new Event('foo');
evt.isTrusted // falseEvent.detail
Event.detail属性只有浏览器的 UI (用户界面)事件才具有。该属性返回一个数值,表示事件的某种信息。具体含义与事件类型相关。
比如,对于click和dblclick事件,Event.detail是鼠标按下的次数(1表示单击,2表示双击,3表示三击);对于鼠标滚轮事件,Event.detail是滚轮正向滚动的距离,负值就是负向滚动的距离,返回值总是3的倍数。
实例方法
Event.preventDefault()
Event.preventDefault方法取消浏览器对当前事件的默认行为。
比如点击链接后,浏览器默认会跳转到另一个页面,使用这个方法以后,就不会跳转了;
再比如,按一下空格键,页面向下滚动一段距离,使用这个方法以后也不会滚动了。
该方法生效的前提是,事件对象的cancelable属性为true,如果为false,调用该方法没有任何效果。
注意,该方法只是取消事件对当前元素的默认影响,不会阻止事件的传播。
如果要阻止传播,可以使用stopPropagation()或stopImmediatePropagation()方法。
Event.stopPropagation()
stopPropagation方法阻止事件在 DOM 中继续传播,防止再触发定义在别的节点上的监听函数,但是不包括在当前节点上其他的事件监听函数。
function stopEvent(e) {
e.stopPropagation();
}
el.addEventListener('click', stopEvent, false);上面代码中,click事件将不会进一步冒泡到el节点的父节点。
Event.stopImmediatePropagation()
Event.stopImmediatePropagation方法阻止同一个事件的其他监听函数被调用,不管监听函数定义在当前节点还是其他节点。也就是说,该方法阻止事件的传播,比Event.stopPropagation()更彻底。
如果同一个节点对于同一个事件指定了多个监听函数,这些函数会根据添加的顺序依次调用。只要其中有一个监听函数调用了Event.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调用了event.stopImmediatePropagation方法,所以l2不会被调用。
Event.composedPath()
Event.composedPath()返回一个数组,成员是事件的最底层节点和依次冒泡经过的所有上层节点。
// HTML 代码如下
// <div>
// <p>Hello</p>
// </div>
var div = document.querySelector('div');
var p = document.querySelector('p');
div.addEventListener('click', function (e) {
console.log(e.composedPath());
}, false);
// [p, div, body, html, document, Window]上面代码中,click事件的最底层节点是p,向上依次是div、body、html、document、Window。
鼠标事件
种类
鼠标事件主要有下面这些,所有事件都继承了MouseEvent接口。
(1)点击事件
鼠标点击相关的有四个事件。
click:按下鼠标(通常是按下主按钮)时触发。dblclick:在同一个元素上双击鼠标时触发。mousedown:按下鼠标键时触发。mouseup:释放按下的鼠标键时触发。
click事件可以看成是两个事件组成的:用户在同一个位置先触发mousedown,再触发mouseup。因此,触发顺序是,mousedown首先触发,mouseup接着触发,click最后触发。
双击时,dblclick事件则会在mousedown、mouseup、click之后触发。
(2)移动事件
鼠标移动相关的有五个事件。
mousemove:当鼠标在一个节点内部移动时触发。当鼠标持续移动时,该事件会连续触发。为了避免性能问题,建议对该事件的监听函数做一些限定,比如限定一段时间内只能运行一次。mouseenter:鼠标进入一个节点时触发,进入子节点不会触发这个事件(详见后文)。mouseover:鼠标进入一个节点时触发,进入子节点会再一次触发这个事件(详见后文)。mouseout:鼠标离开一个节点时触发,离开父节点也会触发这个事件(详见后文)。mouseleave:鼠标离开一个节点时触发,离开父节点不会触发这个事件(详见后文)。
mouseover事件和mouseenter事件,都是鼠标进入一个节点时触发。两者的区别是,mouseenter事件只触发一次,而只要鼠标在节点内部移动,mouseover事件会在子节点上触发多次。
/* HTML 代码如下
<ul>
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
</ul>
*/
var ul = document.querySelector('ul');
// 进入 ul 节点以后,mouseenter 事件只会触发一次
// 以后只要鼠标在节点内移动,都不会再触发这个事件
// event.target 是 ul 节点
ul.addEventListener('mouseenter', function (event) {
event.target.style.color = 'purple';
setTimeout(function () {
event.target.style.color = '';
}, 500);
}, false);
// 进入 ul 节点以后,只要在子节点上移动,mouseover 事件会触发多次
// event.target 是 li 节点
ul.addEventListener('mouseover', function (event) {
event.target.style.color = 'orange';
setTimeout(function () {
event.target.style.color = '';
}, 500);
}, false);上面代码中,在父节点内部进入子节点,不会触发mouseenter事件,但是会触发mouseover事件。
mouseout事件和mouseleave事件,都是鼠标离开一个节点时触发。两者的区别是,在父元素内部离开一个子元素时,mouseleave事件不会触发,而mouseout事件会触发。
/* HTML 代码如下
<ul>
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
</ul>
*/
var ul = document.querySelector('ul');
// 先进入 ul 节点,然后在节点内部移动,不会触发 mouseleave 事件
// 只有离开 ul 节点时,触发一次 mouseleave
// event.target 是 ul 节点
ul.addEventListener('mouseleave', function (event) {
event.target.style.color = 'purple';
setTimeout(function () {
event.target.style.color = '';
}, 500);
}, false);
// 先进入 ul 节点,然后在节点内部移动,mouseout 事件会触发多次
// event.target 是 li 节点
ul.addEventListener('mouseout', function (event) {
event.target.style.color = 'orange';
setTimeout(function () {
event.target.style.color = '';
}, 500);
}, false);上面代码中,在父节点内部离开子节点,不会触发mouseleave事件,但是会触发mouseout事件。
(3)其他事件
contextmenu:按下鼠标右键时(上下文菜单出现前)触发,或者按下“上下文”菜单键时触发。wheel:滚动鼠标的滚轮时触发,该事件继承的是WheelEvent接口。
MouseEvent 接口
MouseEvent接口代表了鼠标相关的事件,单击(click)、双击(dblclick)、松开鼠标键(mouseup)、按下鼠标键(mousedown)等动作,所产生的事件对象都是MouseEvent实例。此外,滚轮事件和拖拉事件也是MouseEvent实例。
MouseEvent接口继承了Event接口,所以拥有Event的所有属性和方法,并且还提供鼠标独有的属性和方法。
浏览器原生提供一个MouseEvent()构造函数,用于新建一个MouseEvent实例。
var event = new MouseEvent(type, options);MouseEvent()构造函数接受两个参数。第一个参数是字符串,表示事件名称;第二个参数是一个事件配置对象,该参数可选。除了Event接口的实例配置属性,该对象可以配置以下属性,所有属性都是可选的。
screenX:数值,鼠标相对于屏幕的水平位置(单位像素),默认值为0,设置该属性不会移动鼠标。screenY:数值,鼠标相对于屏幕的垂直位置(单位像素),其他与screenX相同。clientX:数值,鼠标相对于程序窗口的水平位置(单位像素),默认值为0,设置该属性不会移动鼠标。clientY:数值,鼠标相对于程序窗口的垂直位置(单位像素),其他与clientX相同。ctrlKey:布尔值,是否同时按下了 Ctrl 键,默认值为false。shiftKey:布尔值,是否同时按下了 Shift 键,默认值为false。altKey:布尔值,是否同时按下 Alt 键,默认值为false。metaKey:布尔值,是否同时按下 Meta 键,默认值为false。button:数值,表示按下了哪一个鼠标按键,默认值为0,表示按下主键(通常是鼠标的左键)或者当前事件没有定义这个属性;1表示按下辅助键(通常是鼠标的中间键),2表示按下次要键(通常是鼠标的右键)。buttons:数值,表示按下了鼠标的哪些键,是一个三个比特位的二进制值,默认为0(没有按下任何键)。1(二进制001)表示按下主键(通常是左键),2(二进制010)表示按下次要键(通常是右键),4(二进制100)表示按下辅助键(通常是中间键)。因此,如果返回3(二进制011)就表示同时按下了左键和右键。relatedTarget:节点对象,表示事件的相关节点,默认为null。mouseenter和mouseover事件时,表示鼠标刚刚离开的那个元素节点;mouseout和mouseleave事件时,表示鼠标正在进入的那个元素节点。
MouseEvent 接口的实例属性
MouseEvent.altKey
MouseEvent.ctrlKey
MouseEvent.metaKey
MouseEvent.shiftKey
MouseEvent.altKey、MouseEvent.ctrlKey、MouseEvent.metaKey、MouseEvent.shiftKey这四个属性都返回一个布尔值,表示事件发生时,是否按下对应的键。它们都是只读属性。
altKey属性:Alt 键ctrlKey属性:Ctrl 键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('SHIFT key pressed: ' + e.shiftKey);
}上面代码中,点击网页会输出是否同时按下对应的键。
MouseEvent.button
MouseEvent.button属性返回一个数值,表示事件发生时按下了鼠标的哪个键。该属性只读。
- 0:按下主键(通常是左键),或者该事件没有初始化这个属性(比如
mousemove事件)。 - 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);
}
}MouseEvent.buttons
MouseEvent.buttons属性返回一个三个比特位的值,表示同时按下了哪些键。它用来处理同时按下多个鼠标键的情况。该属性只读。
- 1:二进制为
001(十进制的1),表示按下左键。 - 2:二进制为
010(十进制的2),表示按下右键。 - 4:二进制为
100(十进制的4),表示按下中键或滚轮键。
同时按下多个键的时候,每个按下的键对应的比特位都会有值。比如,同时按下左键和右键,会返回3(二进制为011)。
MouseEvent.clientX
MouseEvent.clientY
MouseEvent.clientX属性返回鼠标位置相对于浏览器窗口左上角的水平坐标(单位像素),MouseEvent.clientY属性返回垂直坐标。这两个属性都是只读属性。
// HTML 代码为
// <body onmousedown="showCoords(event)">
function showCoords(evt){
console.log(
'clientX value: ' + evt.clientX + '\n' +
'clientY value: ' + evt.clientY + '\n'
);
}这两个属性还分别有一个别名MouseEvent.x和MouseEvent.y。
MouseEvent.movementX
MouseEvent.movementY
MouseEvent.movementX属性返回当前位置与上一个mousemove事件之间的水平距离(单位像素)。数值上,它等于下面的计算公式。
currentEvent.movementX = currentEvent.screenX - previousEvent.screenXMouseEvent.movementY属性返回当前位置与上一个mousemove事件之间的垂直距离(单位像素)。数值上,它等于下面的计算公式。
currentEvent.movementY = currentEvent.screenY - previousEvent.screenY。这两个属性都是只读属性。
MouseEvent.screenX
MouseEvent.screenY
MouseEvent.screenX属性返回鼠标位置相对于屏幕左上角的水平坐标(单位像素),MouseEvent.screenY属性返回垂直坐标。这两个属性都是只读属性。
// HTML 代码如下
// <body onmousedown="showCoords(event)">
function showCoords(evt) {
console.log(
'screenX value: ' + evt.screenX + '\n',
'screenY value: ' + evt.screenY + '\n'
);
}MouseEvent.offsetX
MouseEvent.offsetY
MouseEvent.offsetX属性返回鼠标位置与目标节点左侧的padding边缘的水平距离(单位像素),MouseEvent.offsetY属性返回与目标节点上方的padding边缘的垂直距离。这两个属性都是只读属性。
/* HTML 代码如下
<style>
p {
width: 100px;
height: 100px;
padding: 100px;
}
</style>
<p>Hello</p>
*/
var p = document.querySelector('p');
p.addEventListener(
'click',
function (e) {
console.log(e.offsetX);
console.log(e.offsetY);
},
false
);上面代码中,鼠标如果在p元素的中心位置点击,会返回150 150。因此中心位置距离左侧和上方的padding边缘,等于padding的宽度(100像素)加上元素内容区域一半的宽度(50像素)。
MouseEvent.pageX
MouseEvent.pageY
MouseEvent.pageX属性返回鼠标位置与文档左侧边缘的距离(单位像素),MouseEvent.pageY属性返回与文档上侧边缘的距离(单位像素)。它们的返回值都包括文档不可见的部分。这两个属性都是只读。
/* HTML 代码如下
<style>
body {
height: 2000px;
}
</style>
*/
document.body.addEventListener(
'click',
function (e) {
console.log(e.pageX);
console.log(e.pageY);
},
false
);上面代码中,页面高度为2000像素,会产生垂直滚动条。滚动到页面底部,点击鼠标输出的pageY值会接近2000。
MouseEvent.relatedTarget
MouseEvent.relatedTarget属性返回事件的相关节点。对于那些没有相关节点的事件,该属性返回null。该属性只读。
下表列出不同事件的target属性值和relatedTarget属性值义。
| 事件名称 | target 属性 | relatedTarget 属性 |
|---|---|---|
| focusin | 接受焦点的节点 | 丧失焦点的节点 |
| focusout | 丧失焦点的节点 | 接受焦点的节点 |
| mouseenter | 将要进入的节点 | 将要离开的节点 |
| mouseleave | 将要离开的节点 | 将要进入的节点 |
| mouseout | 将要离开的节点 | 将要进入的节点 |
| mouseover | 将要进入的节点 | 将要离开的节点 |
| dragenter | 将要进入的节点 | 将要离开的节点 |
| dragexit | 将要离开的节点 | 将要进入的节点 |
/*
HTML 代码如下
<div id="outer" style="height:50px;width:50px;border: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 (event) {
console.log('进入' + event.target.id + ' 离开' + event.relatedTarget.id);
}, false);
inner.addEventListener('mouseenter', function (event) {
console.log('进入' + event.target.id + ' 离开' + event.relatedTarget.id);
});
inner.addEventListener('mouseout', function (event) {
console.log('离开' + event.target.id + ' 进入' + event.relatedTarget.id);
});
inner.addEventListener("mouseleave", function (event){
console.log('离开' + event.target.id + ' 进入' + event.relatedTarget.id);
});
// 鼠标从 outer 进入inner,输出
// 进入inner 离开outer
// 进入inner 离开outer
// 鼠标从 inner进入 outer,输出
// 离开inner 进入outer
// 离开inner 进入outerMouseEvent 接口的实例方法
MouseEvent.getModifierState()
MouseEvent.getModifierState方法返回一个布尔值,表示有没有按下特定的功能键。它的参数是一个表示功能键的字符串。
document.addEventListener('click', function (e) {
console.log(e.getModifierState('CapsLock'));
}, false);上面的代码可以了解用户是否按下了大写键。
WheelEvent 接口
概述
WheelEvent 接口继承了 MouseEvent 实例,代表鼠标滚轮事件的实例对象。目前,鼠标滚轮相关的事件只有一个wheel事件,用户滚动鼠标的滚轮,就生成这个事件的实例。
浏览器原生提供WheelEvent()构造函数,用来生成WheelEvent实例。
var wheelEvent = new WheelEvent(type, options);WheelEvent()构造函数可以接受两个参数,第一个是字符串,表示事件类型,对于滚轮事件来说,这个值目前只能是wheel。第二个参数是事件的配置对象。该对象的属性除了Event、UIEvent的配置属性以外,还可以接受以下几个属性,所有属性都是可选的。
deltaX:数值,表示滚轮的水平滚动量,默认值是 0.0。deltaY:数值,表示滚轮的垂直滚动量,默认值是 0.0。deltaZ:数值,表示滚轮的 Z 轴滚动量,默认值是 0.0。deltaMode:数值,表示相关的滚动事件的单位,适用于上面三个属性。0表示滚动单位为像素,1表示单位为行,2表示单位为页,默认为0。
实例属性
WheelEvent事件实例除了具有Event和MouseEvent的实例属性和实例方法,还有一些自己的实例属性,但是没有自己的实例方法。
下面的属性都是只读属性。
WheelEvent.deltaX:数值,表示滚轮的水平滚动量。WheelEvent.deltaY:数值,表示滚轮的垂直滚动量。WheelEvent.deltaZ:数值,表示滚轮的 Z 轴滚动量。WheelEvent.deltaMode:数值,表示上面三个属性的单位,0是像素,1是行,2是页。
键盘事件
事件种类
键盘事件由用户击打键盘触发,主要有keydown、keypress、keyup三个事件,它们都继承了KeyboardEvent接口。
keydown:按下键盘时触发。keypress:按下有值的键时触发,即按下 Ctrl、Alt、Shift、Meta 这样无值的键,这个事件不会触发。对于有值的键,按下时先触发keydown事件,再触发这个事件。keyup:松开键盘时触发该事件。
KeyboardEvent
接口概述
KeyboardEvent接口用来描述用户与键盘的互动。这个接口继承了Event接口,并且定义了自己的实例属性和实例方法。
浏览器原生提供KeyboardEvent构造函数,用来新建键盘事件的实例。
new KeyboardEvent(type, options)KeyboardEvent构造函数接受两个参数。第一个参数是字符串,表示事件类型;第二个参数是一个事件配置对象,该参数可选。除了Event接口提供的属性,还可以配置以下字段,它们都是可选。
key:字符串,当前按下的键,默认为空字符串。code:字符串,表示当前按下的键的字符串形式,默认为空字符串。location:整数,当前按下的键的位置,默认为0。ctrlKey:布尔值,是否按下 Ctrl 键,默认为false。shiftKey:布尔值,是否按下 Shift 键,默认为false。altKey:布尔值,是否按下 Alt 键,默认为false。metaKey:布尔值,是否按下 Meta 键,默认为false。repeat:布尔值,是否重复按键,默认为false。
实例属性
KeyboardEvent.altKey
KeyboardEvent.ctrlKey
KeyboardEvent.metaKey
KeyboardEvent.shiftKey
以下属性都是只读属性,返回一个布尔值,表示是否按下对应的键。
KeyboardEvent.altKey:是否按下 Alt 键KeyboardEvent.ctrlKey:是否按下 Ctrl 键KeyboardEvent.metaKey:是否按下 meta 键(Mac 系统是一个四瓣的小花,Windows 系统是 windows 键)KeyboardEvent.shiftKey:是否按下 Shift 键
下面是一个示例。
function showChar(e) {
console.log('ALT: ' + e.altKey);
console.log('CTRL: ' + e.ctrlKey);
console.log('Meta: ' + e.metaKey);
console.log('Shift: ' + e.shiftKey);
}
document.body.addEventListener('keydown', showChar, false);KeyboardEvent.code
KeyboardEvent.code属性返回一个字符串,表示当前按下的键的字符串形式。该属性只读。
下面是一些常用键的字符串形式,其他键请查文档。
- 数字键0 - 9:返回
digit0-digit9 - 字母键A - z:返回
KeyA-KeyZ - 功能键F1 - F12:返回
F1-F12 - 方向键:返回
ArrowDown、ArrowUp、ArrowLeft、ArrowRight - Alt 键:返回
AltLeft或AltRight - Shift 键:返回
ShiftLeft或ShiftRight - Ctrl 键:返回
ControlLeft或ControlRight
KeyboardEvent.key
KeyboardEvent.key属性返回一个字符串,表示按下的键名。该属性只读。
如果按下的键代表可打印字符,则返回这个字符,比如数字、字母。
如果按下的键代表不可打印的特殊字符,则返回预定义的键值,比如 Backspace,Tab,Enter,Shift,Control,Alt,CapsLock,Esc,Spacebar,PageUp,PageDown,End,Home,Left,Right,Up,Down,PrintScreen,Insert,Del,Win,F1~F12,NumLock,Scroll 等。
如果同时按下一个控制键和一个符号键,则返回符号键的键名。比如,按下 Ctrl + a,则返回a;按下 Shift + a,则返回大写的A。
如果无法识别键名,返回字符串Unidentified。
KeyboardEvent.location
KeyboardEvent.location属性返回一个整数,表示按下的键处在键盘的哪一个区域。它可能取以下值。
- 0:处在键盘的主区域,或者无法判断处于哪一个区域。
- 1:处在键盘的左侧,只适用那些有两个位置的键(比如 Ctrl 和 Shift 键)。
- 2:处在键盘的右侧,只适用那些有两个位置的键(比如 Ctrl 和 Shift 键)。
- 3:处在数字小键盘。
KeyboardEvent.repeat
KeyboardEvent.repeat返回一个布尔值,代表该键是否被按着不放,以便判断是否重复这个键,即浏览器会持续触发keydown和keypress事件,直到用户松开手为止。
实例方法
KeyboardEvent.getModifierState()
KeyboardEvent.getModifierState()方法返回一个布尔值,表示是否按下或激活指定的功能键。它的常用参数如下。
Alt:Alt 键CapsLock:大写锁定键Control:Ctrl 键Meta:Meta 键NumLock:数字键盘开关键Shift:Shift 键
if (
event.getModifierState('Control') +
event.getModifierState('Alt') +
event.getModifierState('Meta') > 1
) {
return;
}上面代码表示,只要Control、Alt、Meta里面,同时按下任意两个或两个以上的键就返回。
进度事件
种类
进度事件用来描述资源加载的进度,主要由 AJAX 请求、<img>、<audio>、<video>、<style>、<link>等外部资源的加载触发,继承了ProgressEvent接口。它主要包含以下几种事件。
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';
});上面代码在图片元素加载完成后,为图片元素添加一个finished的 Class。如果加载失败,就把图片元素的样式设置为不显示。
有时候,图片加载会在脚本运行之前就完成,尤其是当脚本放置在网页底部的时候,因此有可能load和error事件的监听函数根本不会执行。所以,比较可靠的方式,是用complete属性先判断一下是否加载完成。
由于 DOM 的元素节点没有提供是否加载错误的属性,所以error事件的监听函数最好放在<img>元素的 HTML 代码中,这样才能保证发生加载错误时百分之百会执行。
<img src="/wrong/url" onerror="this.style.display='none';" />loadend事件的监听函数,可以用来取代abort事件、load事件、error事件的监听函数,因为它总是在这些事件之后发生。
loadend事件本身不提供关于进度结束的原因,但可以用它来做所有加载结束场景都需要做的一些操作。
req.addEventListener('loadend', loadEnd, false);
function loadEnd(e) {
console.log('传输结束,成功失败未知');
}另外,error事件有一个特殊的性质,就是不会冒泡。所以,子元素的error事件,不会触发父元素的error事件监听函数。
ProgressEvent 接口
ProgressEvent接口主要用来描述外部资源加载的进度,比如 AJAX 加载、<img>、<video>、<style>、<link>等外部资源加载。进度相关的事件都继承了这个接口。
浏览器原生提供了ProgressEvent()构造函数,用来生成事件实例。
new ProgressEvent(type, options)ProgressEvent()构造函数接受两个参数。第一个参数是字符串,表示事件的类型,这个参数是必须的。第二个参数是一个配置对象,表示事件的属性,该参数可选。
配置对象除了可以使用Event接口的配置属性,还可以使用下面的属性,所有这些属性都是可选的。
lengthComputable:布尔值,表示加载的总量是否可以计算,默认是false。loaded:整数,表示已经加载的量,默认是0。total:整数,表示需要加载的总量,默认是0。
ProgressEvent具有对应的实例属性。
ProgressEvent.lengthComputableProgressEvent.loadedProgressEvent.total
如果ProgressEvent.lengthComputable为false,ProgressEvent.total实际上是没有意义的。
var p = new ProgressEvent('load', {
lengthComputable: true,
loaded: 30,
total: 100,
});
document.body.addEventListener('load', function (e) {
console.log('已经加载:' + (e.loaded / e.total) * 100 + '%');
});
document.body.dispatchEvent(p);
// 已经加载:30%上面代码先构造一个load事件,抛出后被监听函数捕捉到。
一个实际的例子
var xhr = new XMLHttpRequest();
xhr.addEventListener('progress', updateProgress, false);
xhr.addEventListener('load', transferComplete, false);
xhr.addEventListener('error', transferFailed, false);
xhr.addEventListener('abort', transferCanceled, false);
xhr.open();
function updateProgress(e) {
if (e.lengthComputable) {
var percentComplete = e.loaded / e.total;
} else {
console.log('不能计算进度');
}
}
function transferComplete(e) {
console.log('传输结束');
}
function transferFailed(evt) {
console.log('传输过程中发生错误');
}
function transferCanceled(evt) {
console.log('用户取消了传输');
}上面是下载过程的进度事件,还存在上传过程的进度事件。这时所有监听函数都要放在XMLHttpRequest.upload对象上面。
var xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', updateProgress, false);
xhr.upload.addEventListener('load', transferComplete, false);
xhr.upload.addEventListener('error', transferFailed, false);
xhr.upload.addEventListener('abort', transferCanceled, false);
xhr.open();表单事件
种类
input 事件
input事件当<input>、<select>、<textarea>的值发生变化时触发。
对于复选框(<input type=checkbox>)或单选框(<input type=radio>),用户改变选项时,也会触发这个事件。
另外,对于打开contenteditable(该属性用于使元素可编辑)属性的元素,只要值发生变化,也会触发input事件。
input事件的一个特点,就是会连续触发,比如用户每按下一次按键,就会触发一次input事件。
input事件对象继承了InputEvent接口。
该事件跟change事件很像,不同之处在于input事件在元素的值发生变化后立即发生,而change在元素失去焦点时发生,而内容此时可能已经变化多次。
也就是说,如果有连续变化,input事件会触发多次,而change事件只在失去焦点时触发一次。
下面是<select>元素的例子。
/* HTML 代码如下
<select id="mySelect">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
*/
function inputHandler(e) {
console.log(e.target.value)
}
var mySelect = document.querySelector('#mySelect');
mySelect.addEventListener('input', inputHandler);上面代码中,改变下拉框选项时,会触发input事件,从而执行回调函数inputHandler。
select 事件
select事件当在<input>、<textarea>里面选中文本时触发。
// HTML 代码如下
// <input id="test" type="text" value="Select me!" />
var elem = document.getElementById('test');
elem.addEventListener('select', function (e) {
console.log(e.type); // "select"
}, false);选中的文本可以通过event.target元素的selectionDirection、selectionEnd、selectionStart和value属性拿到。
change 事件
change事件当<input>、<select>、<textarea>的值发生变化时触发。
它与input事件的最大不同,就是不会连续触发,只有当全部修改完成时才会触发,
另一方面input事件必然伴随change事件。具体来说,分成以下几种情况。
- 激活单选框(radio)或复选框(checkbox)时触发。
- 用户提交时触发。比如,从下列列表(select)完成选择,在日期或文件输入框完成选择。
- 当文本框或
<textarea>元素的值发生改变,并且丧失焦点时触发。
对于<select>元素来说,input和change事件基本是等价的。
invalid 事件
用户提交表单时,如果表单元素的值不满足校验条件,就会触发invalid事件。
<form>
<input type="text" required oninvalid="console.log('invalid input')" />
<button type="submit">提交</button>
</form>上面代码中,输入框是必填的。如果不填,用户点击按钮提交时,就会触发输入框的invalid事件,导致提交被取消。
reset 事件
submit 事件
这两个事件发生在表单对象<form>上,而不是发生在表单的成员上。
reset事件当表单重置(所有表单成员变回默认值)时触发。
submit事件当表单数据向服务器提交时触发。注意,submit事件的发生对象是<form>元素,而不是<button>元素,因为提交的是表单,而不是按钮。
InputEvent 接口
InputEvent接口主要用来描述input事件的实例。该接口继承了Event接口,还定义了一些自己的实例属性和实例方法。
浏览器原生提供InputEvent()构造函数,用来生成实例对象。js
new InputEvent(type, options)InputEvent构造函数可以接受两个参数。第一个参数是字符串,表示事件名称,该参数是必需的。第二个参数是一个配置对象,用来设置事件实例的属性,该参数是可选的。配置对象的字段除了Event构造函数的配置属性,还可以设置下面的字段,这些字段都是可选的。
inputType:字符串,表示发生变更的类型。data:字符串,表示插入的字符串。如果没有插入的字符串(比如删除操作),则返回null或空字符串。dataTransfer:返回一个 DataTransfer 对象实例,该属性通常只在输入框接受富文本输入时有效。
InputEvent的实例属性主要就是上面三个属性,这三个实例属性都是只读的。
(1)InputEvent.data
InputEvent.data属性返回一个字符串,表示变动的内容。
// HTML 代码如下
// <input type="text" id="myInput">
var input = document.getElementById('myInput');
input.addEventListener('input', myFunction, false);
function myFunction(e) {
console.log(e.data);
}上面代码中,如果手动在输入框里面输入abc,控制台会先输出a,再在下一行输出b,再在下一行输出c。然后选中abc,一次性将它们删除,控制台会输出null或一个空字符串。
(2)InputEvent.inputType
InputEvent.inputType属性返回一个字符串,表示字符串发生变更的类型。
对于常见情况,Chrome 浏览器的返回值如下。完整列表可以参考文档。
- 手动插入文本:
insertText - 粘贴插入文本:
insertFromPaste - 向后删除:
deleteContentBackward - 向前删除:
deleteContentForward
(3)InputEvent.dataTransfer
InputEvent.dataTransfer属性返回一个 DataTransfer 实例。该属性只在文本框接受粘贴内容(insertFromPaste)或拖拽内容(insertFromDrop)时才有效。