实现跟随鼠标位置的Tooltip

本文最后更新于:2024年7月22日 晚上

实现一个能够鼠标移动的Tooltip,在里面显示一些信息,并且可以进入以进行进一步的互动操作。

基础实现

首先定义一个背景图,当鼠标在背景图中移动的时候,Tooltip显示在鼠标的右下角,并显示当前鼠标在背景图中的坐标。

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
<!-- 
1. 定义一个包裹的容器,并将其position设置为relative
2. 在背景图中设置样式,并添加mousemove的事件监听器,当鼠标在背景图中移动的时候,实时更新数据
3. 设置tooltip的显示内容和样式,并通过style标签动态绑定其位置信息
-->
<template>
<div style="position: relative;">
<div class="canvasArea" @mousemove="handleMouseMove">
</div>
<div class="tooltip" :style="tooltipStyle">
<div>Tooltip</div>
<div>当前鼠标的相对于容器的横坐标是:{{ tooltip.offsetX }}</div>
<div>当前鼠标的相对于容器的纵坐标是:{{ tooltip.offsetY }}</div>
</div>
</div>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue'

const canvasRef = ref(null)
const tooltip = ref({
offsetX: 0,
offsetY: 0,
})

//通过event事件获取鼠标相对于容器的偏移坐标,并赋值给tooltip的属性
function handleMouseMove(event: MouseEvent) {
tooltip.value.offsetX = event.offsetX
tooltip.value.offsetY = event.offsetY
}

//通过计算属性来实时计算tooltip的位置
const tooltipStyle = computed(() => {
return {
left: (tooltip.value.offsetX + 10) + 'px ',
top: (tooltip.value.offsetY + 10) + 'px',
}
})

</script>

<style scoped>
.canvasArea {
width: 800px;
height: 600px;
background-color: #f0f0f0;
}

.tooltip {
background-color: white;
box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.1);
padding: 20px;
border-radius: 10px;
position: absolute;
z-index: 1;
}
</style>

这样就实现了一个跟随鼠标移动的Tooltip,在鼠标移动时更新其内部显示的信息。

此外,发现Tooltip在移动到背景图的边缘时文本会被换行显示,所以给样式添加text-wrap:nowrap来保证Tooltip样式不变。

显隐逻辑

之后需要给背景图添加mouseentermouseleave的事件监听器,当鼠标进入背景图时显示tooltip,当鼠标移开背景图时隐藏tooltip,这时候便可以给tooltip再添加一个visible属性,来控制其显示和隐藏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div style="position: relative;">
<div class="canvasArea"
@mousemove="handleMouseMove"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave">
</div>
<div class="tooltip" :style="tooltipStyle" v-show="tooltip.visible">
<div>Tooltip</div>
<div>当前鼠标的相对于容器的横坐标是:{{ tooltip.offsetX }}</div>
<div>当前鼠标的相对于容器的纵坐标是:{{ tooltip.offsetY }}</div>
</div>
</div>
</template>

添加对应的两个函数,并通过设置一个计时器来延迟Tooltip的消失时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const timeTrigger = ref(null)
const tooltip = ref({
visible: false,
offsetX: 0,
offsetY: 0,
})

// 当鼠标进入时清除计时器,并将tooltip设置为显示
function handleMouseEnter() {
clearTimeout(timeTrigger.value)
tooltip.value.visible = true
}

// 当鼠标离开时触发计时器,在100ms后隐藏tooltip
function handleMouseLeave() {
timeTrigger.value = setTimeout(() => {
tooltip.value.visible = false
}, 100)
}

这样Tooltip就可以实现鼠标在背景图中移动时显示,移出背景图时隐藏的效果了。

可进入

之后是配置可进入的功能,由于在鼠标进入到Tooltip的时候仍然会触发背景图的mouseleave事件,所以需要在Tooltip中也添加一个mouseenter事件,在其中清除隐藏Tooltip的计时器,将Tooltip保持显示

1
2
3
function handleTooltipMouseEnter() {
clearTimeout(timeTrigger.value)
}

这样当鼠标进入Tooltip的时候就可以保持Tooltip的显示,并在其中进行进一步的互动了。但是我们会发现一个问题,那就是鼠标几乎无法进入到Tooltip中,在尝试和观察后发现,导致难以进入的原因是Tooltip太跟手了,也就是鼠标移动到一个位置,Tooltip立马就更新到了新位置,导致鼠标的移动很难“快过”Tooltip,也就难以进入了。

既然是Tooltip的位置更新过快导致的问题,那么可以通过添加一个短暂的延时,来推迟Tooltip的移动,从而让鼠标可以有机会进入Tooltip。

mousemove的handle函数中,将更新位置的部分代码用setTimeout函数包裹

1
2
3
4
5
6
function handleMouseMove(event: MouseEvent) {
setTimeout(() => {
tooltip.value.offsetX = event.offsetX
tooltip.value.offsetY = event.offsetY
}, 10)
}

至此,一个可进入的跟随鼠标移动的Tooltip就实现了。

暗色模式

为了实现暗色模式,可以通过css的媒体监听器来实现暗色模式的不同样式展示

1
2
3
4
5
6
7
8
9
10
@media (prefers-color-scheme: dark) {
.container {
background-color: #333333;
}

.tooltip {
background-color: #464646;
color: lightgray;
}
}

完整代码

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
<template>
<div style="position: relative;">
<div class="canvasArea" @mousemove="handleMouseMove" @mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave">
</div>
<div class="tooltip" :style="tooltipStyle" v-show="tooltip.visible" @mouseenter="handleTooltipMouseEnter">
<div>Tooltip</div>
<div>当前鼠标的相对于容器的横坐标是:{{ tooltip.offsetX }}</div>
<div>当前鼠标的相对于容器的纵坐标是:{{ tooltip.offsetY }}</div>
</div>
</div>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue'

const timeTrigger = ref(null)
const tooltip = ref({
visible: false,
offsetX: 0,
offsetY: 0,
})

function handleMouseMove(event: MouseEvent) {
setTimeout(() => {
tooltip.value.offsetX = event.offsetX
tooltip.value.offsetY = event.offsetY
}, 10)
}

function handleMouseEnter() {
clearTimeout(timeTrigger.value)
tooltip.value.visible = true
}

function handleMouseLeave() {
console.log('leave')
timeTrigger.value = setTimeout(() => {
tooltip.value.visible = false
}, 100)
}

function handleTooltipMouseEnter() {
clearTimeout(timeTrigger.value)
}

const tooltipStyle = computed(() => {
return {
left: (tooltip.value.offsetX + 10) + 'px ',
top: (tooltip.value.offsetY + 10) + 'px',
}
})

</script>

<style scoped>
.canvasArea {
width: 800px;
height: 600px;
background-color: #f0f0f0;
}

.tooltip {
background-color: white;
box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.1);
padding: 20px;
border-radius: 10px;
position: absolute;
z-index: 1;
text-wrap: nowrap;
}
</style>

实现跟随鼠标位置的Tooltip
http://starnight.top/2024/07/16/实现跟随鼠标位置的Tooltip/
作者
Cardy Xie
发布于
2024年7月16日
许可协议