4.处理用户交互

事件的监听与处理

v-on指令(通常用@符合来代替)用来位DOM事件绑定监听

事件监听示例

关于DOM事件的绑定,简单的测试代码如下:

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
<!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>事件绑定</title>
<script src="https://unpkg.com/vue@3.2.47/dist/vue.global.js"></script>
</head>
<body>
<div id="Application">
<div>点击次数:{{count}}</div>
<button @click="click">点击</button>
</div>

<script>
const App = {
data(){
return {
count:0
}
},
methods:{
click(){
this.count += 1
}
}
}
Vue.createApp(App).mount("#Application")
</script>
</body>
</html>

单击页面中的按钮时会执行click函数,从而改变count函数的值,并可以在页面上实时看到变化效果,这是一种基础的用户交互处理方法。

当然我们也可以直接要执行的逻辑代码放入@click赋值的地方

1
<button @click="this.count += 1">点击</button>

但是,通常的处理方法都不是单行的JavaScript代码可以搞定的,更多的时候时采用绑定方法函数的方式来处理事件。

定义的click函数并没有参数,实际上当触发我们绑定的事件函数时,系统自动将当前的Event对象传递到函数中去,如果需要使用到Event对象,定义的处理函数如下:

1
2
3
4
5
6
methods:{
click(event){
console.log(event)
this.count += 1
}
}

Event对象会存储当前事件的很多信息,如事件类型,鼠标位置,键盘按键等。

如果DOM元素绑定执行事件的函数需要传入自定义的参数怎么办?例如:这个计数器的步长是可以设置的,通过函数参数来进行控制,修改click代码如下:

1
2
3
click(step){
this.count += step
}

在进行事件绑定时,可以采用内联处理的方式设置函数的参数

1
<button @click="click(2)">点击</button>

单击页面上的按钮时,可以看到计数器以2为步长进行增加。

如果在自定义参数的基础上,还需要使用Event对象参数,可以使用$event来传递此参数

1
<button @click="click(2,$event)">点击</button>
1
2
3
4
click(step,event){
console.log(event)
this.count += step
}

多事件处理

多事件处理是指对于同一个用户交互事件,需要调用多个方法进行处理

在Vue中,绑定事件时支持使用对多个函数进行绑定调用

1
2
3
4
5
6
7
8
methods:{
click(step){
this.count += step
},
log(event){
console.log(event)
}
}

多事件处理时,在绑定事件都要采用内联调用的方式绑定

1
<button @click="click(2),log($event)">点击</button>

事件修饰符

DOM事件的传递原理:页面触发一个单击事件时,事件首先会从父组件开始依次传递到子组件,这一过程通常被形象的成为事件捕获,当事件传递到最上层的子组件时,其还会逆向地在进行一轮传递,从子组件依次向下传递,这一过程被称为事件冒泡

Vue中使用@click的方式绑定事件时,默认监听的是DOM事件的冒泡阶段,即从子组件传递到父组件的这一过程。

1
2
3
4
5
6
7
8
9
<div @click="click1" style="border:solid red">
外层
<div @click="click2" style="border:solid red">
中层
<div @click="click3" style="border:solid red">
点击
</div>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
methods:{
click(step){
this.count += step
},
log(event){
console.log(event)
},
click1() {
console.log("外层")
},
click2() {
console.log("中层")
},
click3() {
console.log("内层")
}
}

运行上面的代码,单击页面最内层的元素,通过观察控制台的打印可以看到事件函数的调用顺序

内层

中层

外层

如果要监听捕获阶段的事件,就需要使用事件修饰符,事件修饰符capture可以监听事件的实际设置为捕获阶段

1
2
3
4
5
6
7
8
9
<div @click.capture="click1" style="border:solid red">
外层
<div @click.capture="click2" style="border:solid red">
中层
<div @click.capture="click3" style="border:solid red">
点击
</div>
</div>
</div>

运行效果如下:

外层

中层

内层

捕获事件触发的顺序与冒泡事件相反。实际应用中,根据具体需求来选择

理解事件的传递对处理页面用户交互至关重要,但是又很多场景不希望进行传递。

例如,当用户单击内层组件时,我们只想让其触发内层组件的绑定方法,当用户单击外层组件时,只触发外层组件绑定的方法,这就需要stop修饰符。

stop修饰符可以阻止事件的传递

1
2
3
4
5
6
7
8
9
<div @click.stop="click1" style="border:solid red">
外层
<div @click.stop="click2" style="border:solid red">
中层
<div @click.stop="click3" style="border:solid red">
点击
</div>
</div>
</div>

此时单击时,只有被单击的组件绑定的方法才会被调用。

常用的修饰符如下:

事件修饰符作用
stop阻止事件传递
capture监听捕获场景的事件
once只触发一次事件
self当事件对象的target属性是当前组件时才触发事件
Prevent禁止默认事件
passive不仅指默认事件

注意:事件修饰符可以串联使用,例如下面代码既能阻止事件传递,又能控制只触发一次事件。

1
2
3
4
5
6
7
8
9
<div @click.capture="click1" style="border:solid red">
外层
<div @click.capture="click2" style="border:solid red">
中层
<div @click.capture.once="click3" style="border:solid red">
点击
</div>
</div>
</div>

Vue中的事件类型

事件本身是有类型之分的,例如@click绑定的就是元素的单击事件,如果需要通过用户鼠标操作行为来实现更加复杂的交互逻辑,则需要监听更加复杂的鼠标事件,v-on指令进行普通HTML元素的事件绑定时,其支持所有原生的DOM事件,其也可以支持自定义事件。

常用事件类型

click事件是页面开发中常用的交互事件,当HTML元素被单击时会触发此事件,常用的交互事件如下:

事 件意 义可用的元素
click单击事件,当组件被单击时触发大部分HTML元素
dblclick双击事件,当组件被双击时触发大部分HTML元素
focus获取焦点事件,例如输入框开启编辑模式时触发inout、select、textarea等
blur失去焦点事件,例如输入框结束编辑模式时触发inout、select、textarea等
change元素内容改变事件,输入框结束输入后,如果内容有变化,会触发此事件inout、select、textarea等
select元素内容选中事件,输入框中的文本被选中时会触发此事件inout、select、textarea等
mousedown鼠标按键被按下事件大部分HTML元素
mouseup鼠标按键抬起事件大部分HTML元素
mousemove鼠标在组件内移动事件大部分HTML元素
mouseout鼠标移出组件时触发大部分HTML元素
mouseover鼠标移入组件时触发大部分HTML元素
Keydown键盘按键被按下HTML中所有表单元素
keyup键盘按键被抬起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
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
<!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>事件类型</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="Application">
<div @click="click">单击事件</div>
<div @dblclick="dblclick">双击事件</div>
<input @focus="focus" @blur="blur" @change="change" @select="select">
<div @mousedown="mousedown">鼠标按下</div>
<div @mouseup="mouseup">鼠标抬起</div>
<div @mousemove="mousemove">鼠标移动</div>
<div @mouseout="mouseout" mouseover="mouseover">鼠标移入移出</div>
<input @keydown="keydown" @keyup="keyup">
</div>
<script>
const App = {
methods:{
click(){
console.log("单击事件")
},
dblclick(){
console.log("双击事件")
},
focus(){
console.log("获取焦点")
},
blur(){
console.log("失去焦点")
},
change(){
console.log("内容改变")
},
select(){
console.log("文本选中")
},
mousedown(){
console.log("鼠标按键按下")
},
mouseup(){
console.log("鼠标按键抬起")
},
mousemove(){
console.log("鼠标移动")
},
mouseout(){
console.log("鼠标移出")
},
mouseover(){
console.log("鼠标移入")
},
keydown(){
console.log("键盘按键按下")
},
keyup(){
console.log("键盘按键抬起")
},

}
}
Vue.createApp(App).mount("#Application")
</script>
</body>
</html>

对于每一种类型的事件,都可以通过Event对象来获取事件的具体信息,例如在鼠标单击事件中,可以获取用户单击的是左键还是右键

按键修饰符

对键盘按键进行监听时,通过使用keyup参数,如果仅仅对某个按键进行监听,可以通过Event对象来判断,例如监听用户敲击回车键。注意大小写

1
2
3
4
5
6
keyup(event){
console.log("键盘按键抬起")
if (event.key == 'Enter'){
console.log('回车键按下')
}
},

还有一种更简单的方法实现

1
<input @keyup.enter="keyup">

注意:修饰符命名规则与Event对象中属性key值的命名规则不同

Event对象中的属性采用的是大写字母驼峰法:Enter,PageDown

按键修饰符采用的是小写中划线:enter,page-down

Vue还提供了一些特殊的系统按键修饰符,配合其他键盘或鼠标按键进行的。主要有以下4种

ctrl

alt

shift

meta

这些系统修饰符使用的意义只有当用户按下这些键时,对应的键盘或鼠标才会触发,处理组合键经常会用到

1
2
<!-- 用户按下ctrl键的同时,按下鼠标按键才会触发绑定事件函数 -->
<div @mousedown.ctrl="mousedown">鼠标按下</div>
1
2
<!-- 用户按下alt键的同时,按下enter键才会触发绑定事件函数 -->     
<input @keyup.alt.enter="keyup">
1
2
3
4
5
<!-- 注意:
上面的示例只要满足就会触发,如只要满足了(ctrl+鼠标按键)的其他组合(shift+ctrl+鼠标按键)也会触发
想要精准地进行按键修饰,可以用`exact`修饰符,只有精准满足按键条件才会触发 -->

<div @mousedown.ctrl.exact="mousedown">鼠标按下</div>

Vue中还有3个常用地鼠标按键修饰符

left 左键

right 右键

middle 滚轮

1
2
<!-- 只有鼠标左键才会触发  -->
<div @click.left="click">单击事件</div>

范例1:随鼠标移动地小球

X坐标Y坐标意 义
clientXclientY鼠标位置相对当前body容器可视区域的横纵坐标
pageXpageY鼠标位置相对整个页面的横纵坐标
screenXscreenY鼠标位置相对设备屏幕的横纵坐标
offsetXoffsetY鼠标位置相对父容器的横纵坐标
xy与screenX和screenY意义一样

要实现页面元素随鼠标移动很简单,我们只需要监听鼠标移动事件,做好元素坐标的更新即可。

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
<!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>圆球游戏</title>
<script src="https://unpkg.com/vue@next"></script>
<style>
body {
margin: 0;
padding: 0;
}
.container {
margin: 0;
padding: 0;
position: absolute;
width: 440px;
height: 440px;
background-color: blanchedalmond;
display: inline;
}
.ball {
position:absolute;
width: 60px;
height: 60px;
left:100px;
top:100px;
background-color: red;
border-radius: 30px;
z-index:100
}
</style>
</head>
<body>
<div id="Application">
<div class="container" @mousemove.stop="move">
<div class="ball" :style="{left: offsetX+'px', top:offsetY+'px'}">
</div>
</div>
</div>
<script>
const App = {
data() {
return {
// 通过offsetX,offsetY分别用来控制圆球的横纵坐标
offsetX:0,
offsetY:0
}
},
methods: {
move(event) {
// 检查右侧不能超出边界
if (event.clientX + 30 > 440) {
this.offsetX = 440 - 60
// 检查左侧不能超出边界
} else if (event.clientX - 30 < 0) {
this.offsetX = 0
} else {
this.offsetX = event.clientX - 30
}
// 检查下侧不能超出边界
if (event.clientY + 30 > 440) {
this.offsetY = 440 - 60
// 检查上侧不能超出边界
} else if (event.clientY - 30 < 0) {
this.offsetY = 0
} else {
this.offsetY = event.clientY - 30
}
}
}
}
Vue.createApp(App).mount("#Application")
</script>
</body>
</html>

范例2:弹球游戏

1
2
3
4
5
6
7
8
9
10
11
<div id="Application">
<!-- 游戏区域 -->
<div class="container">
<!-- 底部挡板 -->
<div class="board" :style="{left: boardX + 'px'}"></div>
<!-- 弹球 -->
<div class="ball" :style="{left: ballX+'px', top: ballY+'px'}"></div>
<!-- 游戏结束提示 -->
<h1 v-if="fail" style="text-align: center;">游戏失败</h1>
</div>
</div>

如上所示,底部挡板元素可以通过键盘来控制移动,游戏失败的提示文案默认是隐藏的,当游戏失败后控制器展示。

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
<style>
body {
margin: 0;
padding: 0;
}
.container {
position: relative;
margin: 0 auto;
width: 440px;
height: 440px;
background-color: blanchedalmond;
}
.ball {
position:absolute;
width: 30px;
height: 30px;
left:0px;
top:0px;
background-color:orange;
border-radius: 30px;
}
.board {
position:absolute;
left: 0;
bottom: 0;
height: 10px;
width: 80px;
border-radius: 5px;
background-color: red;
}
</style>

在控制页面布局时,当父容器position属性设置为relative时,子组件的position属性设置为absolute,可以将子组件相对应父组件进行绝对布局。实现此游戏的JavaScripts逻辑并不复杂

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
<script>
const App = {
data() {
return {
// 控制挡板位置
boardX:0,
// 控制弹球位置
ballX:0,
ballY:0,
// 控制弹球移动速度
rateX:0.1,
rateY:0.1,
// 控制结束游戏提示的展示
fail:false
}
},
// 组件生命周期函数,组件加载时会调用
mounted() {
// 添加键盘事件
this.enterKeyup();
// 随机弹球的运动速度和方向
this.rateX = (Math.random() + 0.1)
this.rateY = (Math.random() + 0.1)
// 开启计数器,控制弹球移动
this.timer = setInterval(()=>{
// 到达右侧边缘进行反弹
if (this.ballX + this.rateX >= 440 - 30) {
this.rateX *= -1
}
// 到达左侧边缘进行反弹
if (this.ballX + this.rateX <= 0) {
this.rateX *= -1
}
// 到达上侧边缘进行反弹
if (this.ballY + this.rateY <= 0) {
this.rateY *= -1
}
this.ballX += this.rateX
this.ballY += this.rateY
// 失败判定
if (this.ballY >= 440 - 30 - 10) {
// 挡板接住了弹球,进行反弹
if (this.boardX <= this.ballX + 30 && this.boardX + 80 >= this.ballX) {
this.rateY *= -1
} else {
// 没有接住弹球,游戏结束
clearInterval(this.timer)
this.fail = true
}
}
},2)
},
methods: {
// 控制挡板移动
keydown(event){
if (event.key == "ArrowLeft") {
if (this.boardX > 10) {
this.boardX -= 20
}
} else if (event.key == "ArrowRight") {
if (this.boardX < 440 - 80) {
this.boardX += 20
}
}
},
enterKeyup() {
document.addEventListener("keydown", this.keydown);
}
}
}
Vue.createApp(App).mount("#Application")
</script>

小节练习

问:Vue中绑定监听事件的指令是什么。

提示:v-on用法,熟悉使用@缩写模式

问:事件修饰符

提示:stop,prevent,capture,once等

问:如何处理组合键

提示:Vue提供了4个常用的系统修饰键,其可以与其他按键修饰符组合用,从而实现组合键监听