vue3 - 如何使用指令轻松实现鼠标水平滚动

引言

在开发一体机项目时,我遇到了一个棘手的问题:
由于一体机的特殊性,其界面样式基于移动端设计,但浏览器内核仍属于 PC 端。因此,内部元素既要支持移动端的 touch 事件,又要兼容 PC 端的 mouse 事件。

实现思路

为了让鼠标滚轮能够控制水平滚动,我们可以监听 wheel 事件,并根据滚动方向调整 scrollLeft 值。

1
2
3
4
5
6
const onWheel = (e: any) => {
const delta = Math.max(-1, Math.min(1, e.wheelDelta || -e.detail))
const target = e.currentTarget as DraggableElement | null
if (target) target.scrollLeft -= delta * 450
e.preventDefault()
}

onWheel 函数接收一个事件对象 e
计算滚轮滚动的方向和幅度,结果保存在 delta 变量中。
获取触发事件的目标元素 target,并将其类型断言为 DraggableElement
如果 target 存在,调整其 scrollLeft 属性,使页面滚动。
调用 e.preventDefault() 阻止默认的滚动行为。
这样,当用户滚动鼠标滚轮时,页面会根据滚动方向和幅度进行水平滚动。

拖拽滚动

除了滚轮滚动,我们还需要支持鼠标拖拽滚动,这需要监听以下事件:

  • 鼠标按下事件
  • 鼠标移出事件
  • 鼠标移动事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 鼠标移出事件
const onMouseUpOrLeave = (el: DraggableElement) => () => {
el.isMouseDown = false
}
// 鼠标移动事件
const onMouseMove = (el: DraggableElement) => (e: MouseEvent) => {
if (el.isMouseDown) {
const moveX = e.clientX
const target = e.currentTarget as DraggableElement
const scrollX = moveX - el.startX
if (target) {
target.scrollLeft = el.scrollLeft - scrollX
el.scrollLeft = target.scrollLeft
el.startX = e.clientX
}
}
}
// 鼠标按下事件
const onMouseDown = (el: DraggableElement) => (e: any) => {
el.isMouseDown = true
el.startX = e.clientX
el.scrollLeft = e.currentTarget.scrollLeft
}

这三个函数用于实现一个简单的拖拽滚动效果,具体功能如下:
onMouseDown 函数:

这是一个鼠标按下事件处理函数。

当鼠标按下时,设置 el.isMouseDowntrue,表示鼠标已经按下。

记录鼠标按下时的 X 坐标 e.clientX

记录当前元素的 scrollLeft 值到 el.scrollLeft,用于后续计算滚动距离。

onMouseUpOrLeave 函数:
这是一个鼠标松开或移出事件处理函数。
当鼠标松开或移出时,设置 el.isMouseDown 为 false,表示鼠标已经松开或移出了元素。
onMouseMove 函数:
这是一个鼠标移动事件处理函数。
当鼠标移动时,如果 el.isMouseDowntrue,表示鼠标处于按下状态,执行滚动操作。
计算鼠标移动的距离 moveX - el.startX
根据鼠标移动的距离调整元素的 scrollLeft 值,实现滚动效果。
更新 el.scrollLeft,以便下一次移动时继续计算滚动距离。

这些函数结合起来,可以实现一个拖拽滚动的效果,当用户按住鼠标并拖动时,元素会根据鼠标的移动进行滚动。

实现源码

dragUtil.ts
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
interface DraggableElement extends HTMLElement {
isMouseDown?: boolean
scrollLeft: number
startX: number
}
// 鼠标按下事件
const onMouseDown = (el: DraggableElement) => (e: any) => {
el.isMouseDown = true
el.startX = e.clientX
el.scrollLeft = e.currentTarget.scrollLeft
}
// 滚轮事件
const onWheel = (e: any) => {
const delta = Math.max(-1, Math.min(1, e.wheelDelta || -e.detail))
const target = e.currentTarget as DraggableElement | null
if (target) target.scrollLeft -= delta * 450
e.preventDefault()
}
// 鼠标移出事件
const onMouseUpOrLeave = (el: DraggableElement) => () => {
el.isMouseDown = false
}
// 鼠标移动事件
const onMouseMove = (el: DraggableElement) => (e: MouseEvent) => {
if (el.isMouseDown) {
const moveX = e.clientX
const target = e.currentTarget as DraggableElement
const scrollX = moveX - el.startX
if (target) {
target.scrollLeft = el.scrollLeft - scrollX
el.scrollLeft = target.scrollLeft
el.startX = e.clientX
}
}
}
export default {
created(el: DraggableElement) {
el.startX = 0
el.isMouseDown = false
el.scrollLeft = 0
el.style.setProperty('user-select', 'none')
},
mounted(el: DraggableElement) {
el.addEventListener('mousedown', onMouseDown(el) as EventListener)
el.addEventListener('wheel', onWheel as EventListener)
el.addEventListener('mouseup', onMouseUpOrLeave(el) as EventListener)
el.addEventListener('mouseleave', onMouseUpOrLeave(el) as EventListener)
el.addEventListener('mousemove', onMouseMove(el) as EventListener)
},
unmounted(el: DraggableElement) {
el.removeEventListener('mousedown', onMouseDown(el) as EventListener)
el.removeEventListener('wheel', onWheel as EventListener)
el.removeEventListener('mouseup', onMouseUpOrLeave(el) as EventListener)
el.removeEventListener('mouseleave', onMouseUpOrLeave(el) as EventListener)
el.removeEventListener('mousemove', onMouseMove(el) as EventListener)
},
}

注册指令

main.ts
1
2
import dragUtil from './util/dragUtil.ts'
app.directive('drag', dragUtil)

使用指令

1
2
 <div v-drag>
</div>