JavaScript-事件(一)
事件
一、EventTarget 接口
事件的本质是程序各个组成部分之间的一种通信方式,也是异步编程的一种实现。DOM 支持大量的事件,本章开始介绍 DOM 的事件编程。
1、概述
DOM 的事件操作(监听和触发),都定义在EventTarget
接口。所有节点对象都部署了这个接口,其他一些需要事件通信的浏览器内置对象(比如,XMLHttpRequest
、AudioNode
、AudioContext
)也部署了这个接口。
该接口主要提供三个实例方法。
addEventListener
:绑定事件的监听函数removeEventListener
:移除事件的监听函数dispatchEvent
:触发事件
2、EventTarget.addEventListener()
EventTarget.addEventListener()
用于在当前节点或对象上,定义一个特定事件的监听函数。一旦这个事件发生,就会执行监听函数。该方法没有返回值。
target.addEventListener(type, listener[, useCapture]);
// 事件名称,监听函数 [,是否在捕获阶段触发]
该方法接受三个参数。
type
:事件名称,大小写敏感。listener
:监听函数。事件发生时,会调用该监听函数。useCapture
:布尔值,表示监听函数是否在捕获阶段(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');
}
});
上面代码中,addEventListener
方法的第二个参数,就是一个具有handleEvent
方法的对象。
其次,第三个参数除了布尔值useCapture
,还可以是一个属性配置对象。该对象有以下属性。
capture
:布尔值,表示该事件是否在捕获阶段
触发监听函数。once
:布尔值,表示监听函数是否只触发一次,然后就自动移除。passive
:布尔值,表示监听函数不会调用事件的preventDefault
方法。如果监听函数调用了,浏览器将忽略这个要求,并在监控台输出一行警告。
如果希望事件监听函数只执行一次,可以打开属性配置对象的once
属性。
element.addEventListener('click', function (event) {
// 只执行一次的代码
}, {once: true});
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
传递了一个参数。
监听函数内部的this
,指向当前事件所在的那个对象。
// HTML 代码如下
// <p id="para">Hello</p>
var para = document.getElementById('para');
para.addEventListener('click', function (e) {
console.log(this.nodeName); // "P"
}, false);
上面代码中,监听函数内部的this
指向事件所在的对象para
。
3、EventTarget.removeEventListener()
EventTarget.removeEventListener
方法用来移除addEventListener
方法添加的事件监听函数。该方法没有返回值。
div.addEventListener('click', listener, false);
div.removeEventListener('click', listener, false);
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
方法也是无效的,因为第三个参数不一样。
4、EventTarget.dispatchEvent()
EventTarget.dispatchEvent
方法在当前节点上触发指定事件,从而触发监听函数的执行。该方法返回一个布尔值,只要有一个监听函数调用了Event.preventDefault()
,则返回值为false
,否则为true
。
target.dispatchEvent(event)
dispatchEvent
方法的参数是一个Event
对象的实例(详见《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('事件未取消');
}
二、事件模型
1、监听函数
浏览器的事件模型,就是通过监听函数(listener)对事件做出反应。事件发生后,浏览器监听到了这个事件,就会执行对应的监听函数。这是事件驱动编程模式(event-driven)的主要编程方式。
JavaScript 有三种方法,可以为事件绑定监听函数。
1.2 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()">
1.2 元素节点的事件属性 (例:el.onclick)
元素节点对象的事件属性,同样可以指定监听函数。
window.onload = doSomething; // 注意这里是函数名,不加圆括号
div.onclick = function (event) { // onclick是元素的一个属性,它指向一个函数,在触发点击时执行此函数
console.log('触发事件');
};
使用这个方法指定的监听函数,也是只会在冒泡阶段触发。
注意,这种方法与 HTML 的on-
属性的差异是,它的值是函数名(doSomething
),而不像后者,必须给出完整的监听代码(doSomething()
)。
1.3 EventTarget.addEventListener()
所有 DOM 节点实例都有addEventListener
方法,用来为该节点定义事件的监听函数。
window.addEventListener('load', doSomething, false); // 注意这里是函数名,不加圆括号
addEventListener
方法的详细介绍,参见EventTarget
章节。
1.4 小结
上面三种方法,
第一种“HTML 的 on- 属性”,违反了 HTML 与 JavaScript 代码相分离的原则,将两者写在一起,不利于代码分工,因此不推荐使用。
第二种“元素节点的事件属性”的缺点在于,同一个事件只能定义一个监听函数,也就是说,如果定义两次onclick
属性,后一次定义会覆盖前一次。因此,也不推荐使用。
第三种EventTarget.addEventListener
是推荐的指定监听函数的方法。它有如下优点:
- 同一个事件可以添加多个监听函数。
- 能够指定在哪个阶段(捕获阶段还是冒泡阶段)触发监听函数。
- 除了 DOM 节点,其他对象(比如
window
、XMLHttpRequest
等)也有这个接口,它等于是整个 JavaScript 统一的监听函数接口。
2、this 的指向 (指向元素节点)
监听函数内部的this
指向触发事件的那个元素节点。(三种事件绑定的this全指向元素节点)
<button id="btn" onclick="console.log(this.id)">点击</button>
执行上面代码,点击后会输出btn
。
其他两种监听函数的写法,this
的指向也是如此。
// HTML 代码如下
// <button id="btn">点击</button>
var btn = document.getElementById('btn');
// 写法一
btn.onclick = function () {
console.log(this.id);
};
// 写法二
btn.addEventListener(
'click',
function (e) {
console.log(this.id);
},
false
);
上面两种写法,点击按钮以后也是输出btn
。
3、事件的传播(捕获、目标、冒泡)
一个事件发生后,会在子元素和父元素之间传播(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); // true 表示在捕获阶段触发
p.addEventListener('click', callback, true); // true 表示在捕获阶段触发
div.addEventListener('click', callback, false);// fasle 表示在冒泡阶段触发
p.addEventListener('click', callback, false); // fasle 表示在冒泡阶段触发
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
。
捕获阶段:
window(浏览器对象)--> document(文档对象) --> html --> body --> div --> p (点击目标,在目标阶段)
冒泡阶段:
p (点击目标,在目标阶段)--> div --> body --> html --> document(文档对象) --> window(浏览器对象)
4、事件的代理
由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)。
var ul = document.querySelector('ul');
ul.addEventListener('click', function (event) {
if (event.target.tagName.toLowerCase() === 'li') { // 浏览器假定click嵌套最深的元素是目标元素
// some code
}
});
上面代码中,click
事件的监听函数定义在<ul>
节点,但是实际上,它处理的是子节点<li>
的click
事件。这样做的好处是,只要定义一个监听函数,就能处理多个子节点的事件,而不用在每个<li>
节点上定义监听函数。而且以后再添加子节点,监听函数依然有效。
阻止事件冒泡 event.stopPropagation()
如果希望事件到某个节点为止,不再传播,可以使用事件对象的stopPropagation
方法。
// 事件传播到 p 元素后,就不再向下传播了
p.addEventListener('click', function (event) {
event.stopPropagation(); // 该方法在事件对象event上
}, true); // true表示在捕获阶段绑定事件监听函数
// 事件冒泡到 p 元素后,就不再向上冒泡了
p.addEventListener('click', function (event) {
event.stopPropagation();
}, false); // 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 对象
1. 概述
事件发生以后,会产生一个事件对象,作为参数传给监听函数。浏览器原生提供一个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); // 触发ev实例,该实例是look事件
上面代码新建一个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的click事件
上面代码中,p
元素发出一个click
事件,该事件默认不会冒泡。div.addEventListener
方法指定在冒泡阶段监听,因此监听函数不会触发。如果写成div.addEventListener('click', callback, true)
,那么在“捕获阶段”可以监听到这个事件。
另一方面,如果这个事件在div
元素上触发。
div.dispatchEvent(click);
那么,不管div
元素是在冒泡阶段监听,还是在捕获阶段监听,都会触发监听函数。因为这时div
元素是事件的目标,不存在是否冒泡的问题,div
元素总是会接收到事件,因此导致监听函数生效。
2. 实例属性
2.1 Event.bubbles 是否会冒泡,只读,Event.eventPhase 返回整数,表示事件所处阶段,只读
Event.bubbles
属性返回一个布尔值,表示当前事件是否会冒泡。该属性为只读属性,一般用来了解 Event 实例是否可以冒泡。前面说过,除非显式声明,Event
构造函数生成的事件,默认是不冒泡的。
Event.eventPhase
属性返回一个整数常量,表示事件目前所处的阶段。该属性只读。
var phase = event.eventPhase;
Event.eventPhase
的返回值有四种可能。
- 0,事件目前没有发生。
- 1,事件目前处于捕获阶段,即处于从祖先节点向目标节点的传播过程中。
- 2,事件到达目标节点,即
Event.target
属性指向的那个节点。 - 3,事件处于冒泡阶段,即处于从目标节点向祖先节点的反向传播过程中。
2.2 Event.cancelable 是否可取消默认行为,Event.cancelBubble是否阻止冒泡,event.defaultPrevented 是否调用过取消默认行为方法
Event.cancelable
属性返回一个布尔值,表示事件是否可以取消。该属性为只读属性,一般用来了解 Event 实例的特性。
大多数浏览器的原生事件是可以取消的。比如,取消click
事件,点击链接将无效。但是除非显式声明,Event
构造函数生成的事件,默认是不可以取消的。
var evt = new Event('foo');
evt.cancelable // false 默认不可取消
当Event.cancelable
属性为true
时,调用Event.preventDefault()
就可以取消这个事件,阻止浏览器对该事件的默认行为。
如果事件不能取消,调用Event.preventDefault()
会没有任何效果。所以使用这个方法之前,最好用Event.cancelable
属性判断一下是否可以取消。
function preventEvent(event) {
if (event.cancelable) {
event.preventDefault();
} else {
console.warn('This event couldn\'t be canceled.');
console.dir(event);
}
}
Event.cancelBubble
属性是一个布尔值,如果设为true
,相当于执行Event.stopPropagation()
,可以阻止事件的传播。
Event.defaultPrevented
属性返回一个布尔值,表示该事件是否调用过Event.preventDefault
方法。该属性只读。
if (event.defaultPrevented) {
console.log('该事件已经取消了');
}
2.3 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);
上面代码中,<em>
是<p>
的子节点,点击<em>
或者点击<p>
,都会导致监听函数执行。这时,e.target
总是指向原始点击位置的那个节点,而e.currentTarget
指向事件传播过程中正在经过的那个节点。由于监听函数只有事件经过时才会触发,所以e.currentTarget
总是等同于监听函数内部的this
。
2.4 Event.type 事件类型(如:‘click’)
Event.type
属性返回一个字符串,表示事件类型。事件的类型是在生成事件的时候指定的。该属性只读。
var evt = new Event('foo');
evt.type // "foo"
2.5 Event.timeStamp 相对于打开网页后的毫秒时间戳
Event.timeStamp
属性返回一个毫秒时间戳,表示事件发生的时间。它是相对于网页加载成功开始计算的。
var evt = new Event('foo');
evt.timeStamp // 3683.6999999995896
它的返回值有可能是整数,也有可能是小数(高精度时间戳),取决于浏览器的设置。
下面是一个计算鼠标移动速度的例子,显示每秒移动的像素数量。
例子:计算鼠标移动速度
var previousX;
var previousY;
var previousT;
window.addEventListener('mousemove', function(event) {
if (
previousX !== undefined &&
previousY !== undefined &&
previousT !== undefined
) {
var deltaX = event.screenX - previousX;
var deltaY = event.screenY - previousY;
var deltaD = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
var deltaT = event.timeStamp - previousT;
console.log(deltaD / deltaT * 1000);
}
previousX = event.screenX;
previousY = event.screenY;
previousT = event.timeStamp;
});
2.6 Event.isTrusted 是否由真实用户行为产生的事件
Event.isTrusted
属性返回一个布尔值,表示该事件是否由真实的用户行为产生。比如,用户点击链接会产生一个click
事件,该事件是用户产生的;Event
构造函数生成的事件,则是脚本产生的。
var evt = new Event('foo');
evt.isTrusted // false
上面代码中,evt
对象是脚本产生的,所以isTrusted
属性返回false
。
2.7 Event.detail 事件的细节(单击or双击等)
Event.detail
属性只有浏览器的 UI (用户界面)事件才具有。该属性返回一个数值,表示事件的某种信息。具体含义与事件类型相关。比如,对于click
和dblclick
事件,Event.detail
是鼠标按下的次数(1
表示单击,2
表示双击,3
表示三击);对于鼠标滚轮事件,Event.detail
是滚轮正向滚动的距离,负值就是负向滚动的距离,返回值总是3的倍数。
// HTML 代码如下
// <p>Hello</p>
function giveDetails(e) {
console.log(e.detail); // 快速点击的次数
}
document.querySelector('p').onclick = giveDetails;
3、实例方法
3.1 Event.preventDefault() 取消浏览器对当前事件的默认行为
Event.preventDefault
方法取消浏览器对当前事件的默认行为。比如点击链接后,浏览器默认会跳转到另一个页面,使用这个方法以后,就不会跳转了;再比如,按一下空格键,页面向下滚动一段距离,使用这个方法以后也不会滚动了。该方法生效的前提是,事件对象的cancelable
属性为true
,如果为false
,调用该方法没有任何效果。
注意,该方法只是取消事件对当前元素的默认影响,不会阻止事件的传播。如果要阻止传播,可以使用stopPropagation()
或stopImmediatePropagation()
方法。
// HTML 代码为
// <input type="checkbox" id="my-checkbox" />
var cb = document.getElementById('my-checkbox');
cb.addEventListener(
'click',
function (e){ e.preventDefault(); },
false
);
上面代码中,浏览器的默认行为是单击会选中单选框,取消这个行为,就导致无法选中单选框。
利用这个方法,可以为文本输入框设置校验条件。如果用户的输入不符合条件,就无法将字符输入文本框。
例子:只能输入字母的输入框
// HTML 代码为
// <input type="text" id="my-input" />
var input = document.getElementById('my-input');
input.addEventListener('keypress', checkName, false);
function checkName(e) {
if (e.charCode < 97 || e.charCode > 122) {
e.preventDefault();
}
}
上面代码为文本框的keypress
事件设定监听函数后,将只能输入小写字母,否则输入事件的默认行为(写入文本框)将被取消,导致不能向文本框输入内容。
3.2 Event.stopPropagation() 阻止事件传播
stopPropagation
方法阻止事件在 DOM 中继续传播,防止再触发定义在别的节点上的监听函数,但是不包括在当前节点上其他的事件监听函数。
function stopEvent(e) {
e.stopPropagation(); // 阻止事件冒泡
}
el.addEventListener('click', stopEvent, false);
上面代码中,click
事件将不会进一步冒泡到el
节点的父节点。
3.3 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
不会被调用。
3.4 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
。
四、鼠标事件
1、鼠标事件的种类
鼠标事件指与鼠标相关的事件,继承了MouseEvent
接口。具体的事件主要有以下一些。
click
:按下鼠标(通常是按下主按钮)时触发。【单击】dblclick
:在同一个元素上双击鼠标时触发。【双击】mousedown
:按下鼠标键时触发。【按下】mouseup
:释放按下的鼠标键时触发。【抬起】mousemove
:当鼠标在一个节点内部移动时触发。当鼠标持续移动时,该事件会连续触发。为了避免性能问题,建议对该事件的监听函数做一些限定,比如限定一段时间内只能运行一次。【经过(多次)】mouseenter
:鼠标进入一个节点时触发,进入子节点不会触发这个事件(详见后文)。【进入(单次)】mouseover
:鼠标进入一个节点时触发,进入子节点会再一次触发这个事件(详见后文)。【进入+子节点】mouseleave
:鼠标离开一个节点时触发,离开父节点不会触发这个事件(详见后文)。【离开(单次)】mouseout
:鼠标离开一个节点时触发,离开父节点会触发这个事件(详见后文)。【离开+子节点】contextmenu
:按下鼠标右键时(上下文菜单出现前)触发,或者按下“上下文菜单键”时触发。【右键】wheel
:滚动鼠标的滚轮时触发,该事件继承的是WheelEvent
接口。【滚轮】
click
事件指的是,用户在同一个位置先完成mousedown
动作,再完成mouseup
动作。因此,触发顺序是,mousedown
首先触发,mouseup
接着触发,click
最后触发。
dblclick
事件则会在mousedown
、mouseup
、click
之后触发。
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
事件。
2、MouseEvent 接口概述
MouseEvent
接口代表所有鼠标事件所产生的对象都是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 键(win键),默认值为false
。button
:数值,表示按下了哪一个鼠标按键,默认值为0
,表示按下主键(通常是鼠标的左键)或者当前事件没有定义这个属性;1
表示按下辅助键(通常是鼠标的中间键),2
表示按下次要键(通常是鼠标的右键)。buttons
:数值,表示按下了鼠标的哪些键,是一个三个比特位的二进制值,默认为0
(没有按下任何键)。1
(二进制001
)表示按下主键(通常是左键),2
(二进制010
)表示按下次要键(通常是右键),4
(二进制100
)表示按下辅助键(通常是中间键)。因此,如果返回3
(二进制011
)就表示同时按下了左键和右键。relatedTarget
:节点对象,表示事件的相关节点,默认为null
。mouseenter
和mouseover
事件时,表示鼠标刚刚离开的那个元素节点;mouseout
和mouseleave
事件时,表示鼠标正在进入的那个元素节点。
下面是一个例子。
var event = new MouseEvent('click2', {
'bubbles': true,
'cancelable': true
});
var cb = document.getElementById('checkbox');
cb.addEventListener('click2',function(){ // 绑定事件监听函数
console.log(22) // 被执行
})
cb.dispatchEvent(event);// 触发事件
上面代码生成一个鼠标点击事件,并触发该事件。
3、MouseEvent 接口的实例属性
3.1 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);
}
上面代码中,点击网页会输出是否同时按下对应的键。
3.2 MouseEvent.button 鼠标的哪个键,MouseEvent.buttons同时按哪些键
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
属性返回一个三个比特位的值,表示同时按下了哪些键。它用来处理同时按下多个鼠标键的情况。该属性只读。
- 1:二进制为
001
(十进制的1),表示按下左键。 - 2:二进制为
010
(十进制的2),表示按下右键。 - 4:二进制为
100
(十进制的4),表示按下中键或滚轮键。
同时按下多个键的时候,每个按下的键对应的比特位都会有值。比如,同时按下左键和右键,会返回3(二进制为011)。
document.body.addEventListener('mousemove',function(e){ // 注意,用click时一直都是0
console.log(e.buttons)
})
// 未按下任何键时是 0
// 按下左键 1 (001)
// 按下右键 2 (010)
// 按下中键 4 (100)
// 按下左键和右键 3 (011)
// 按下左键和中键 5 (101)
// 按下右键和中键 6 (110)
// 按下左、中、右键 7 (111)
3.3 MouseEvent.clientX 相对浏览器X坐标,MouseEvent.clientY 相对浏览器Y坐标
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
。
3.4 MouseEvent.movementX 上一个鼠标经过事件的X距离,MouseEvent.movementY 上一个鼠标经过事件的Y距离
MouseEvent.movementX
属性返回当前位置与上一个mousemove
事件之间的水平距离(单位像素)。数值上,它等于下面的计算公式。
currentEvent.movementX = currentEvent.screenX - previousEvent.screenX
MouseEvent.movementY
属性返回当前位置与上一个mousemove
事件之间的垂直距离(单位像素)。数值上,它等于下面的计算公式。
currentEvent.movementY = currentEvent.screenY - previousEvent.screenY。
这两个属性都是只读属性。
- 点赞
- 收藏
- 关注作者
评论(0)