4-1.事件分类
常见事件大致可总结如下。更多事件分类可详见MDN
4-1-1.资源事件
事件名称 | 何时触发 |
---|---|
error | 资源加载失败时。 |
abort | 正在加载资源已经被中止时。 |
DOMContentLoaded | 当 HTML 的加载和处理均完成,DOM 被完全构建完成时。 |
load | 资源及其相关资源已完成加载。 |
beforeunload | window ,document 及其资源即将被卸载。 |
unload | 文档或一个依赖资源正在被卸载。 |
4-1-2.网络事件
事件名称 | 何时触发 |
---|---|
online | 浏览器已获得网络访问。 |
offline | 浏览器已失去网络访问。 |
4-1-3.焦点事件
事件名称 | 何时触发 |
---|---|
focus | 元素获得焦点(不会冒泡)。 |
blur | 元素失去焦点(不会冒泡) |
4-1-4.WebSocket
事件
事件名称 | 何时触发 |
---|---|
open | WebSocket 连接已建立。 |
message | 通过 WebSocket 接收到一条消息。 |
error | WebSocket 连接异常被关闭(比如有些数据无法发送)。 |
close | WebSocket 连接已关闭。 |
4-1-5.会话历史事件
事件名称 | 何时触发 |
---|---|
pagehide | 页面显示。 |
pagehide | 页面隐藏。 |
popstate | 页面切换。 |
4-1-6.CSS
动画事件
事件名称 | 何时触发 |
---|---|
animationstart | 某个 CSS 动画开始时触发。 |
animationend | 某个 CSS 动画完成时触发。 |
animationiteration | 某个 CSS 动画完成后重新开始时触发。 |
4-1-7.CSS
过渡事件
事件名称 | 何时触发 |
---|---|
transitionstart | CSS 过渡开始。 |
transitioncancel | CSS 过渡取消。 |
transitionend | CSS 过渡结束。 |
transitionrun | CSS 过渡开始运行(在transitionstart 之前执行)。 |
4-1-8.表单事件
事件名称 | 何时触发 |
---|---|
reset | 点击重置按钮时 |
submit | 点击提交按钮 |
4-1-9.打印事件
事件名称 | 何时触发 |
---|---|
beforeprint | 打印机已经就绪时触发。 |
afterprint | 打印机关闭时触发。 |
4-1-10.文本写作事件
该类事件可用于检测中文输入法的文本拼音合成。常用于 input
输入框。
事件名称 | 何时触发 |
---|---|
compositionstart | 开始 |
compositionupdate | 更新 |
compositionend | 结束 |
4-1-11.视图事件
事件名称 | 何时触发 |
---|---|
fullscreenchange | 元素由正常展示转为全屏展示或者由全屏展示转为正常展示 |
fullscreenerror | 转为全屏展示时出错(可能是技术原因,也可能是权限被拒绝) |
resize | The document view has been resized. |
scroll | The document view or an element has been scrolled. |
4-1-12.剪贴板事件
事件名称 | 何时触发 |
---|---|
cut | 已经剪贴选中的文本内容并且复制到了剪贴板。即 ctrl + x 或 command + x 。 |
copy | 已经把选中的文本内容复制到了剪贴板。即 ctrl + c 或 command + c 。 |
paste | 从剪贴板复制的文本内容被粘贴。即 ctrl + v 或 command + v 。 |
4-1-13.键盘事件
事件名称 | 何时触发 |
---|---|
keydown | 按下任意按键。 |
keypress | 除 Shift 、Fn 、CapsLock 外的任意键被按住。(连续触发。) |
keyup | 释放任意按键。 |
4-1-14.鼠标事件
事件名称 | 何时触发 |
---|---|
click | 在元素上按下并释放任意鼠标按键。 |
contextmenu | 右键点击(在右键菜单显示前触发)。 |
dblclick | 在元素上双击鼠标按钮。 |
mousedown | 在元素上按下任意鼠标按钮。 |
mouseenter | 指针移到有事件监听的元素内。 |
mouseleave | 指针移出元素范围外(不冒泡)。 |
mousemove | 指针在元素内移动时持续触发。 |
mouseover | 指针移到有事件监听的元素或者它的子元素内。 |
mouseout | 指针移出元素,或者移到它的子元素上。 |
mouseup | 在元素上释放任意鼠标按键。 |
pointerlockchange | 鼠标被锁定或者解除锁定发生时。 |
pointlockerror | 可能因为一些技术的原因鼠标锁定被禁止时。 |
select | 有文本被选中。 |
wheel | 滚轮向任意方向滚动。 |
4-1-15.拖放事件
事件名称 | 何时触发 |
---|---|
drag | 正在拖动元素或文本选区(在此过程中持续触发,每350ms 触发一次)。 |
dragend | 拖放操作结束(松开鼠标按钮或按下Esc 键)。 |
dragenter | 被拖动的元素或文本选区移入有效释放目标区。 |
dragstart | 用户开始拖动HTML 元素或选中的文本。 |
dragleave | 被拖动的元素或文本选区移出有效释放目标区。 |
dragover | 被拖动的元素或文本选区正在有效释放目标上被拖动 (在此过程中持续触发,每350ms 触发一次)。 |
drop | 元素在有效释放目标区上释放。 |
|
4-2.事件处理程序
我们可以给事件添加事件处理程序,以在事件被触发时,做出对应响应。
添加方式有三种:
4-2-1.HTML
特性
处理程序可以设置在 HTML
中名为 on<event>
的特性(attribute
)中。
譬如我们添加一个点击事件:
<!-- !!!这里是handleClick() 而不是handleClick 代表事件触发后,执行该函数 -->
<button onclick="handleClick()">click</button>
<script>
function handleClick () {
alert('The element is clicked!')
}
</script>
TIP
HTML
特性名是大小写不敏感的,所以 ONCLICK
和 onClick
以及 onCLICK
都一样可以运行。
但是特性通常是小写的:onclick
。
4-2-2.DOM
属性
还可以使用 DOM
属性(property
)on<event>
来分配处理程序。
<button id="btn" onclick="handleClick()">click</button>
<script>
function handleClick () {
alert('The element is clicked!')
}
// 该方式会覆盖HTML的onclick特性
btn.onclick = function () { // 另外,这里绑定的是函数体,而不是函数执行结果。这与HTML的onclick特性的写法有所不同。
alert('The button is clicked!')
}
</script>
然后,HTML
特性或者 DOM
属性这两种方式,都有缺点。它们都不能绑定同一个事件类型的多个事件处理程序。
<button id="btn">click</button>
<script>
btn.onclick = function () {
alert('The element is clicked!')
}
// 多次绑定click事件处理程序 该处理程序会覆盖上面的处理程序
btn.onclick = function () {
alert('The button is clicked!')
}
</script>
于是 w3c
提出了新的解决方案,即 element.addEventListener()
。
4-2-3.addEventListener
addEventListener
可用于给目标 DOM
元素添加处理程序,其语法如下:
element.addEventListener(eventType, eventCallback, options)
eventType
: 事件类型。譬如click
、focus
、mouseover
等。eventCallback
: 事件处理程序。options
: 配置参数。有两种形式布尔值或者对象。不同形式有不同的配置语义。
当 options
为布尔值时,代表 useCapture
,其值有以下:
false
: 默认值。代表处理程序在事件冒泡阶段触发。true
: 代表处理程序在事件捕获阶段触发。
当 options
为对象时,其可配置参数如下:
capture
: 与useCapture
一致。默认值false
,代表处理程序在事件冒泡阶段触发。true
代表处理程序在事件捕获阶段触发。once
: 表示处理程序是否只触发一次。passive
: 表示处理程序是否要触发event.preventDefault()
。设置为true
时,事件处理程序内部的event.preventDefault()
将不起作用。另外部分事件scroll
mousemove
等,为了更好的性能体验,passive
默认为true
。
使用 addEventListener
能够给同一事件类型添加多个事件处理程序:
<button id="btn">click</button>
<script>
var btn = document.querySelector('#btn')
btn.addEventListener('click', function () {
alert('The element is clicked!')
})
btn.addEventListener('click', function () {
alert('The button is clicked!')
})
</script>
另外,为了防止内存泄漏,在页面销毁时或者事件需要失效时,可以主动使用 removeEventListener
移除某一事件的处理程序。
<button id="target">click me</button>
<button id="remove">remove</button>
<script>
var target = document.querySelector('#target')
var remove = document.querySelector('#remove')
// 给target添加上事件处理程序
target.addEventListener('click', function () {
alert('The element is clicked!')
})
var handler = function () {
alert('The button is clicked!')
}
target.addEventListener('click', handler)
// 点击remove按钮时 移除target上的事件处理程序
remove.addEventListener('click', function () {
target.removeEventListener('click', handler)
})
</script>
4-3.冒泡与捕获
前文提到了 addEventListener
可配置 useCapture
、capture
来决定事件的触发阶段是在事件冒泡还是事件捕获。
事件冒泡是 IE
提出的浏览器标准,事件捕获则是 NetScape
提出的浏览器标准。
我们先看下事件冒泡:
<style>
.box {
margin: 10px;
border: 1px solid skyblue;
cursor: pointer;
}
</style>
<!-- 事件冒泡 -->
<div id="top1" class="box">Top
<div id="middle1" class="box">Middle
<p id="bottom1" class="box">Bottom</p>
</div>
</div>
<script>
var top = document.querySelector('#top1')
var middle = document.querySelector('#middle1')
var bottom = document.querySelector('#bottom1')
top.addEventListener('click', function (e) {
alert('Top')
})
middle.addEventListener('click', function (e) {
alert('Middle')
})
bottom.addEventListener('click', function (e) {
alert('Bottom')
})
</script>
当点击 Bottom
元素时,会依次弹出 Bottom
、Middle
、Top
。
因为事件会默认在冒泡阶段触发。
下面看一下事件捕获:
<!-- 事件捕获 -->
<div id="top2" class="box">Top
<div id="middle2" class="box">Middle
<p id="bottom2" class="box">Bottom</p>
</div>
</div>
<script>
var top = document.querySelector('#top2')
var middle = document.querySelector('#middle2')
var bottom = document.querySelector('#bottom2')
top.addEventListener('click', function (e) {
alert('Top')
}, {
capture: true
})
middle.addEventListener('click', function (e) {
alert('Middle')
}, {
capture: true
})
bottom.addEventListener('click', function (e) {
alert('Bottom')
}, {
capture: true
})
</script>
当点击 Bottom
元素时,会依次弹出 Top
、Middle
、Bottom
。
因为事件会默认在捕获阶段触发。
然而下面的例子,在点击 Bottom
元素后,则是依次弹出 Middle
、Bottom
、Top
:
<div id="top3" class="box">Top
<div id="middle3" class="box">Middle
<p id="bottom3" class="box">Bottom</p>
</div>
</div>
<script>
var top = document.querySelector('#top3')
var middle = document.querySelector('#middle3')
var bottom = document.querySelector('#bottom3')
top.addEventListener('click', function (e) {
alert('Top')
})
middle.addEventListener('click', function (e) {
alert('Middle')
}, {
capture: true
})
bottom.addEventListener('click', function (e) {
alert('Bottom')
})
</script>
这是为了兼容两套标准事件冒泡和事件捕获。
W3C
规定事件流先执行事件捕获阶段,然后再执行事件冒泡阶段。
4-4.DOM事件流
首先我们感受下 DOM
事件流:
<div id="top4" class="box">Top
<div id="middle4" class="box">Middle
<p id="bottom4" class="box">Bottom</p>
</div>
</div>
<script>
var top = document.querySelector('#top4')
var middle = document.querySelector('#middle4')
var bottom = document.querySelector('#bottom4')
top.addEventListener('click', function (e) {
alert('Top')
})
top.addEventListener('click', function (e) {
alert('Top-capture')
}, {
capture: true
})
middle.addEventListener('click', function (e) {
alert('Middle')
})
middle.addEventListener('click', function (e) {
alert('Middle-capture')
}, {
capture: true
})
bottom.addEventListener('click', function (e) {
alert('Bottom')
})
bottom.addEventListener('click', function (e) {
alert('Bottom-capture')
}, {
capture: true
})
</script>
上述代码分别给元素添加上了冒泡事件和捕获事件。
在点击 Bottom
元素后,会依次弹出 Top-capture
、Middle-capture
、Bottom-capture
、Top
、Middle
、Bottom
。
TIP
要说明的一点,事件流始终是按照事件捕获 =>
事件冒泡这样的方式进行的。
而addEventListener
的 useCapture
、capture
参数只是声明事件在哪个事件流阶段触发,并不会影响到事件流的默认执行。
如果想要阻止事件冒泡或者事件捕获的话,可以使用 event.stopPropagation()
。
4-5.浏览器默认行为
许多事件会自动触发浏览器执行某些默认行为。譬如:
- 点击一个链接 —— 触发导航(
navigation
)到该URL
。 - 点击表单的提交按钮 —— 触发提交到服务器的行为。
4-5-1.阻止默认行为
有两种方式来告诉浏览器我们不希望它执行默认行为:
- 主流的方式是使用
event
对象。有一个event.preventDefault()
方法。 - 如果处理程序是使用
on<event>
(而不是addEventListener
)分配的,那返回false
也同样有效。
<a href="/" onclick="return false">return false</a>
<a href="/" onclick="event.preventDefault()">preventDefault</a>
4-5-2.passive
addEventListener
的 passive
参数将阻止处理程序调用 event.preventDefault()
。
<a href="/" id="passive-href">passive-href</a>
<script>
var passive = document.querySelector('#passive-href')
passive.addEventListener('click', function (event) {
event.preventDefault()
}, {
passive: true
})
</script>
上述链接即使已经添加了 event.preventDefault()
,但在点击之后依然会跳转。
根据规范,passive
选项的默认值始终为 false
。但是,这引入了处理某些触摸事件(以及其他)的事件监听器在尝试处理滚动时阻止浏览器的主线程的可能性,从而导致滚动处理期间性能可能大大降低。
为防止出现此问题,某些浏览器(特别是 Chrome
和 Firefox
)已将文档级节点 Window
,Document
和 Document.body
的 touchstart
和 touchmove
事件的 passive
选项的默认值更改为 true
。
这可以防止调用事件监听器,因此在用户滚动时无法阻止页面呈现。
添加 passive
参数后,touchmove
事件不会阻塞页面的滚动(同样适用于鼠标的滚轮事件)。在这里查看demo(需要翻墙)。
更多详细信息可见MDN
4-5-3.defaultPrevented
defaultPrevented
属性可以用来判断事件流阶段(事件冒泡、事件捕获)是否调用了 event.preventDefault()
。
譬如下例,我们通常如果想要阻止事件冒泡触发父元素的事件时,通常会使用 e.stopPropagation()
。
<div id="box">Box
<a href="/" id="href">click</a>
</div>
<script>
var box = document.querySelector('#box')
var href = document.querySelector('#href')
box.addEventListener('click', function () {
alert('box')
})
href.addEventListener('click', function (e) {
e.preventDefault()
// 阻止传播
e.stopPropagation()
alert('href')
})
</script>
当然,这样能够实现我们想要的效果。但由于事件委托的优势,一般情况下不推荐使用 stopPropagation
方法。
我们可以使用 defaultPrevented
属性来判断:
<div id="box">Box
<a href="/" id="href">click</a>
</div>
<script>
var box = document.querySelector('#box')
var href = document.querySelector('#href')
box.addEventListener('click', function (e) {
if (e.defaultPrevented) return
alert('box')
})
href.addEventListener('click', function (e) {
e.preventDefault()
alert('href')
})
</script>
4-6.自定义事件
我们不但可以利用 JavaScript
来创建自定义事件,甚至也可以创建 click
、mousemove
等浏览器的内置事件。
4-6-1.Event
new Event
是创建自定义事件的基础用法。
new Event(type[, options])
type
: 自定义事件名。options
: 相关配置。有以下两个参数:bubbles
: 事件能否冒泡。默认值为false
。cancelable
: 事件能否阻止默认浏览器行为。该属性配合dispatchEvent
有相关用法。默认值为false
。
另外W3C为了更好的区分事件类别,还额外添加许多事件类别:
MouseEvent
WheelEvent
KeyboardEvent
- ...
相对 Event
来说,上述事件类别支持更多针对每个事件的详细参数配置。
4-6-2.CustomEvent
此外,还有一个特殊的事件类别 CustomEvent
。
它相对 Event
有一个额外的 detail
属性:
var customEvent = new CustomEvent('greet', {
bubbles: true,
cancelable: true,
detail: {
tip: '自定义事件'
}
})
console.log(customEvent)
4-6-3.dispatchEvent
在创建自定义事件之后,可以利用 element.dispatchEvent(event)
来触发事件。
然后,如果该自定义事件绑定了事件处理程序,那么浏览器会对它做出反应。
如果事件是用 bubbles
标志创建的,那么它会冒泡。
如果事件是用 cancelable
标志创建的,那么它可以阻止默认行为。
<a href="/" onclick="alert('click')" id="dispatch-a">测试a链接</a>
<button id="dispatch-button">dispatchEvent</button>
<script>
var target = document.querySelector('#dispatch-a')
var button = document.querySelector('#dispatch-button')
var clickEvent = new Event('click', {
bubbles: false,
cancelable: true
})
// 绑定事件处理程序
target.addEventListener('click', function (e) {
alert('ClickEvent is triggered')
// 阻止a链接默认行为
e.preventDefault()
})
// 主动触发
button.addEventListener('click', function () {
// dispatchEvent的返回值与cancelable、e.preventDefault()有关联
var result = target.dispatchEvent(clickEvent)
console.log(result) // false
})
</script>
TIP
由 dispatchEvent
触发的事件,event.isTrusted
属性为 false
。
而由用户操作触发的事件,event.isTrusted
属性为 true
。
可根据该属性来判断事件的产生来源。
而关于 dispatchEvent
的返回值与 cancelable
、e.preventDefault()
的联系可以总结如下:
- 当
cancelable
为true
, 而且事件处理程序中调用了e.preventDefault()
,此时dispatchEvent
的返回值为false
。 - 上述条件有一条不满足,则
dispatchEvent
的返回值为true
。
该特性可以用来判断自定义事件是否调用了 e.preventDefault()
。
TIP
另外,dispatchEvent
的执行是同步的。