常见的事件列表

  • 鼠标事件:
    • click —— 当鼠标点击一个元素时(触摸屏设备会在点击时生成)。
    • mouseover / mouseout —— 当鼠标指针移入/离开一个元素时。
    • mousedown / mouseup —— 当在元素上按下/释放鼠标按钮时。
    • mousemove —— 当鼠标移动时。
  • 键盘事件:
    • keydown 和 keyup —— 当按下和松开一个按键时。
  • 表单(form)元素事件:
    • submit —— 当访问者提交了一个
      时。
    • focus —— 当访问者聚焦于一个元素时,例如聚焦于一个
  • Document 事件:
    • DOMContentLoaded —— 当 HTML 的加载和处理均完成,DOM 被完全构建完成时。
  • CSS 事件:
    • transitionend —— 当一个 CSS 动画完成时。

认识事件流

  • 事实上对于事件有一个概念叫做事件流,为什么会产生事件流呢?
    • 我们可以想到一个问题:当我们在浏览器上对着一个元素点击时,你点击的不仅仅是这个元素本身;
    • 这是因为我们的HTML元素是存在父子元素叠加层级的;
    • 比如一个span元素是放在div元素上的,div元素是放在body元素上的,body元素是放在html元素上的;

事件冒泡和事件捕获

  • 我们会发现默认情况下事件是从最内层的span向外依次传递的顺序,这个顺序我们称之为事件冒泡;
  • 事实上,还有另外一种监听事件流的方式就是从外层到内层(body -> span),这种称之为事件捕获;
  • 为什么会产生两种不同的处理流呢?
    • 这是因为早期浏览器开发时,不管是IE还是Netscape公司都发现了这个问题;
    • 但是他们采用了完全相反的事件流来对事件进行了传递;
    • IE采用了事件冒泡的方式,Netscape采用了事件捕获的方式;
  • 那么我们如何去监听事件捕获的过程呢? image.png
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box {
display: flex;
justify-content: center;
align-items: center;
width: 200px;
height: 200px;
background-color: orange;
}

.box span {
width: 100px;
height: 100px;
background-color: red;
}
</style>
</head>

<body>

<div class="box">
<span></span>
</div>

<script>

// 1.获取元素
var spanEl = document.querySelector("span")
var divEl = document.querySelector("div")
var bodyEl = document.body


// 默认情况下是事件冒泡
spanEl.addEventListener("click", function () {
console.log("span元素发生了点击~冒泡")
})
divEl.addEventListener("click", function () {
console.log("div元素发生了点击~冒泡")
})
bodyEl.addEventListener("click", function () {
console.log("body元素发生了点击~冒泡")
})

// 设置希望监听事件捕获的过程
spanEl.addEventListener("click", function () {
console.log("span元素发生了点击~捕获")
}, true)
divEl.addEventListener("click", function () {
console.log("div元素发生了点击~捕获")
}, true)
bodyEl.addEventListener("click", function () {
console.log("body元素发生了点击~捕获")
}, true)

</script>

</body>

</html>

image.png

事件捕获和冒泡的过程

  • 如果我们都监听,那么会按照如下顺序来执行:
  • 捕获阶段:
    • 事件(从 Window)向下走近元素。
  • 目标阶段:
    • 事件到达目标元素。
  • 冒泡阶段:
    • 事件从元素上开始冒泡。
  • 事实上,我们可以通过event对象来获取当前的阶段:
    • eventPhase
  • **开发中通常会使用事件冒泡,所以事件捕获了解即可。 **

事件对象

  • 当一个事件发生时,就会有和这个事件相关的很多信息:
    • 比如事件的类型是什么,你点击的是哪一个元素,点击的位置是哪里等等相关的信息;
    • 那么这些信息会被封装到一个Event对象中,这个对象由浏览器创建,称之为event对象;
    • 该对象给我们提供了想要的一些属性,以及可以通过该对象进行某些操作
  • 如何获取这个event对象呢?
    • event对象会在传入的事件处理(event handler)函数回调时,被系统传入;
    • 我们可以在回调函数中拿到这个event对象;image.png
  • 这个对象中都有哪些常见的属性和操作呢?

event常见的属性和方法

  • 常见的属性:
    • type:事件的类型;
    • target:当前事件发生的元素;
    • currentTarget:当前处理事件的元素;
    • eventPhase:事件所处的阶段;
    • offsetX、offsetY:事件发生在元素内的位置;
    • clientX、clientY:事件发生在客户端内的位置;
    • pageX、pageY:事件发生在客户端相对于document的位置;
    • screenX、screenY:事件发生相对于屏幕的位置;
  • 常见的方法:
    • preventDefault:取消事件的默认行为;
    • stopPropagation:阻止事件的进一步传递(冒泡或者捕获都可以阻止);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box {
display: flex;
width: 200px;
height: 200px;
background-color: orange;
}

span {
width: 100px;
height: 100px;
background-color: #f00;
}
</style>
</head>
<body>

<div class="box">
<span class="btn">
<button>按钮</button>
</span>
</div>

<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>

<script>

var divEl = document.querySelector("div")
var btnEl = document.querySelector(".btn")

// btnEl.onclick = function() {
// console.log("按钮发生了点击~")
// }

divEl.onclick = function(event) {
// 1.偶尔会使用
console.log("事件类型:", event.type)
console.log("事件阶段:", event.eventPhase)

// 2.比较少使用
console.log("事件元素中位置", event.offsetX, event.offsetY)
console.log("事件客户端中位置", event.clientX, event.clientY)
console.log("事件页面中位置", event.pageX, event.pageY)
console.log("事件在屏幕中位置", event.screenX, event.screenY)

// 3.target/currentTarget
console.log(event.target)
console.log(event.currentTarget)
console.log(event.currentTarget === event.target)
}

</script>

</body>
</html>

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box {
display: flex;
width: 200px;
height: 200px;
background-color: orange;
}

.box span {
width: 100px;
height: 100px;
background-color: #f00;
}
</style>
</head>

<body>

<a href="http://www.baidu.com">百度一下</a>

<div class="box">
<span>
<button>按钮</button>
</span>
</div>

<script>
// 1.阻止默认行为
// var aEl = document.querySelector("a")
// aEl.onclick = function(event) {
// console.log("a元素发生了点击~")
// event.preventDefault()
// }

// 2.阻止事件进一步传递
var btnEl = document.querySelector("button")
var spanEl = document.querySelector("span")
var divEl = document.querySelector("div")

divEl.addEventListener("click", function (event) {
console.log("div的事件捕获监听~")
// event.stopPropagation()
}, true)
spanEl.addEventListener("click", function () {
console.log("span的事件捕获监听~")
// event.stopPropagation()
}, true)
btnEl.addEventListener("click", function (event) {
console.log("button的事件捕获监听~")
// event.stopPropagation()
}, true)

divEl.addEventListener("click", function () {
console.log("div的事件冒泡监听~")
})
spanEl.addEventListener("click", function (event) {
console.log("span的事件冒泡监听~")
event.stopPropagation()
})
btnEl.addEventListener("click", function () {
console.log("button的事件冒泡监听~")
})

</script>

</body>

</html>

image.png

事件处理中的this

  • 在函数中,我们也可以通过this来获取当前的发生元素:image.png
  • **这是因为在浏览器内部,调用event handler是绑定到当前的target上的 **

EventTarget类

  • 我们会发现,所有的节点、元素都继承自EventTarget
    • 事实上Window也继承自EventTarget;
  • 那么这个EventTarget是什么呢?
    • EventTarget是一个DOM接口,主要用于添加、删除、派发Event事件;
  • EventTarget常见的方法:
    • addEventListener:注册某个事件类型以及事件处理函数;
    • removeEventListener:移除某个事件类型以及事件处理函数;
    • dispatchEvent:派发某个事件类型到EventTarget上; image.png
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>

<button>按钮</button>

<script>

var btnEl = document.querySelector("button")

// 1.将监听函数移除的过程
var foo = function() {
console.log("监听到按钮的点击")
}
btnEl.addEventListener("click", foo)

// 需求: 过5s钟后, 将这个事件监听移除掉
setTimeout(function() {
btnEl.removeEventListener("click", foo)
}, 5000)

// 2.这种做法是无法移除的
// btnEl.addEventListener("click", function() {
// console.log("btn监听的处理函数~")
// })

// setTimeout(function() {
// btnEl.removeEventListener("click", function() {})
// }, 5000)

</script>

</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>

<body>

<script>

// eventtarget就可以实现类似于事件总线的效果
window.addEventListener("shiyi", function () {
console.log("监听到shiyi的呼唤~")
})

setTimeout(function () {
window.dispatchEvent(new Event("shiyi"))
}, 5000)

</script>

</body>

</html>

事件委托(event delegation)

  • 事件冒泡在某种情况下可以帮助我们实现强大的事件处理模式 – 事件委托模式(也是一种设计模式)
  • 那么这个模式是怎么样的呢?
    • 因为当子元素被点击时,父元素可以通过冒泡可以监听到子元素的点击;
    • 并且可以通过event.target获取到当前监听的元素;
  • 案例:一个ul中存放多个li,点击某一个li会变成红色
    • 方案一:监听每一个li的点击,并且做出相应;
    • 方案二:在ul中监听点击,并且通过event.target拿到对应的li进行处理;
      • 因为这种方案并不需要遍历后给每一个li上添加事件监听,所以它更加高效;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.active {
color: red;
font-size: 20px;
background-color: orange;
}
</style>
</head>

<body>

<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
<li>10</li>
</ul>

<script>

// 1.每一个li都监听自己的点击, 并且有自己的处理函数(自己的函数)
// var liEls = document.querySelectorAll("li")
// for (var liEl of liEls) {
// // 监听点击
// liEl.onclick = function(event) {
// event.currentTarget.classList.add("active")
// }
// }

// 2.统一在ul中监听
// var ulEl = document.querySelector("ul")
// ulEl.onclick = function(event) {
// console.log("点击了某一个li", event.target)
// event.target.classList.add("active")
// }

// 3.新需求: 点击li变成active, 其他的取消active
var ulEl = document.querySelector("ul")
var activeLiEl = null
ulEl.onclick = function (event) {
// 1.将之前的active移除掉
// for (var i = 0; i < ulEl.children.length; i++) {
// var liEl = ulEl.children[i]
// if (liEl.classList.contains("active")) {
// liEl.classList.remove("active")
// }
// }

// 1.找到active的li, 移除掉active
// var activeLiEl = ulEl.querySelector(".active")
// if (activeLiEl) {
// activeLiEl.classList.remove("active")
// }

// 1.变量记录的方式
// edge case
if (activeLiEl) {
activeLiEl.classList.remove("active")
}

// 2.给点击的元素添加active
event.target.classList.add("active")

// 3.记录最新的active对应的li
activeLiEl = event.target
}

</script>

</body>

</html>

事件委托的标记

  • 某些事件委托可能需要对具体的子组件进行区分,这个时候我们可以使用**data-***对其进行标记:
  • 比如多个按钮的点击,区分点击了哪一个按钮: image.png

常见的鼠标事件

  • 接下来我们来看一下常见的鼠标事件(不仅仅是鼠标设备,也包括模拟鼠标的设备,比如手机、平板电脑)
  • 常见的鼠标事件: image.png

mouseover和mouseenter的区别

  • mouseenter和mouseleave
    • 不支持冒泡
    • 进入子元素依然属于在该元素内,没有任何反应
  • mouseover和mouseout
    • 支持冒泡
    • 进入元素的子元素时
      • 先调用父元素的mouseout
      • 再调用子元素的mouseover
      • 因为支持冒泡,所以会将mouseover传递到父元素中;

常见的键盘事件

  • 常见的键盘事件:image.png
  • 事件的执行顺序是 onkeydown、onkeypress、onkeyup
    • down事件先发生;
    • press发生在文本被输入;
    • up发生在文本输入完成
  • 我们可以通过key和code来区分按下的键:
    • code:“按键代码”(”KeyA”,”ArrowLeft” 等),特定于键盘上按键的物理位置。
    • key:字符(”A”,”a” 等),对于非字符的按键,通常具有与 code 相同的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>

<input type="text">
<button>搜索</button>

<script>

var inputEl = document.querySelector("input")
var btnEl = document.querySelector("button")

// inputEl.onkeydown = function() {
// console.log("onkeydown")
// }
// inputEl.onkeypress = function() {
// console.log("onkeypress")
// }
// inputEl.onkeyup = function(event) {
// console.log(event.key, event.code)
// }

// 1.搜索功能
btnEl.onclick = function() {
console.log("进行搜索功能", inputEl.value)
}

inputEl.onkeyup = function(event) {
if (event.code === "Enter") {
console.log("进行搜索功能", inputEl.value)
}
}

// 2.按下s的时候, 搜索自动获取焦点
document.onkeyup = function(event) {
if (event.code === "KeyS") {
inputEl.focus()
}
}

</script>

</body>
</html>

常见的表单事件

针对表单也有常见的事件: image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>

<form action="/abc">
<input type="text">
<textarea name="" id="" cols="30" rows="10"></textarea>

<button type="reset">重置</button>
<button type="submit">提交</button>
</form>

<script>
var inputEl = document.querySelector("input")

// 1.获取焦点和失去焦点
// inputEl.onfocus = function() {
// console.log("input获取到了焦点")
// }
// inputEl.onblur = function() {
// console.log("input失去到了焦点")
// }

// 2.内容发生改变/输入内容
// 输入的过程: input
// 内容确定发生改变(离开): change
// inputEl.oninput = function() {
// console.log("input事件正在输入内容", inputEl.value)
// }
// inputEl.onchange = function() {
// console.log("change事件内容发生改变", inputEl.value)
// }

// 3.监听重置和提交
var formEl = document.querySelector("form")
formEl.onreset = function(event) {
console.log("发生了重置事件")
event.preventDefault()
}

formEl.onsubmit = function(event) {
console.log("发生了提交事件")
// axios库提交
event.preventDefault()
}

</script>

</body>
</html>

文档加载事件

  • DOMContentLoaded:浏览器已完全加载 HTML,并构建了 DOM 树,但像 和样式表之类的外部资源可能尚未加载 完成。
  • load:浏览器不仅加载完成了 HTML,还加载完成了所有外部资源:图片,样式等。 image.png