🐛【vue3指令系列】:轻松实现横向拖拽滚动

2,189 阅读4分钟

image.png

前言

这是vue3指令插件的第二篇,这次实现的是一个拖拽滚动的插件。主要的效果是可以通过拖拽和滚轮来滚动横向列表。类似移动端的拖拽滚动。

这个插件可以用在那种横向列表,又需要滚动的需求上。

  • 目前实现效果:

    • 滚轮滚动

    • 拖拽滚动

效果展示

动画.gif

实现思路

实现思路这边的部分描述,是我直接用ai生成的。小偷一点懒

滚轮滚动

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()
}

这段代码的作用是处理鼠标滚轮事件,主要是为了实现拖拽滚动的效果。具体来说:

  1. onWheel 函数接收一个事件对象 e
  2. 计算滚轮滚动的方向和幅度,结果保存在delta变量中。
  3. 获取触发事件的目标元素target,并将其类型断言为 DraggableElement
  4. 如果 target存在,调整其scrollLeft属性,使页面滚动。
  5. 调用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
    }
  }
}
// 鼠标按下事件
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.isMouseDownfalse,表示鼠标已经松开或移出了元素。

onMouseMove函数:

  • 这是一个鼠标移动事件处理函数。
  • 当鼠标移动时,如果el.isMouseDowntrue,表示鼠标处于按下状态,执行滚动操作。
  • 计算鼠标移动的距离 moveX - el.startX
  • 根据鼠标移动的距离调整元素的 scrollLeft 值,实现滚动效果。
  • 更新 el.scrollLeft,以便下一次移动时继续计算滚动距离。

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

实现源码

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)
  },
}

使用方法

注册指令

  • 这是一个vue3的指令,在main.ts里面注册这个指令

  • import dragAndDrop from './modules/dragAndDrop'
    app.directive('dragAndDrop',dragAndDrop)
    

    为了方便我这里,将所有的指令的注册都放到 index.ts文件里面 然后统一注册

image-20250106004306415.png

image-20250106004326966.png

**index.ts文件代码**

```ts
import type { App } from 'vue'
import tableAutoScroll from './modules/tableAutoScroll'
import dragAndDrop from './modules/dragAndDrop'
const directives: Record<string, any> = {
  tableAutoScroll,
  dragAndDrop,
}
/**
 * @function 批量注册指令
 * @param app vue 实例对象
 */
export const install = (app: App): void => {
  Object.keys(directives).forEach((key) => {
    app.directive(key, directives[key]) // 将每个directive注册到app中
  })
}
```

在组件中使用

```vue
<template>
  <article class="container">
    <p>横向滚动</p>
    <section v-dragAndDrop class="transverse">
      <el-row>
        <el-col v-for="item in 20" :key="item" :span="6">
          <el-card>
            <p>卡片 {{ item }}</p>
          </el-card>
        </el-col>
      </el-row>
    </section>
  </article>
</template>

<script setup lang="ts"></script>

<style lang="scss" scoped>
.container {
  height: 100%;
  display: flex;
  flex-direction: column;
  padding: 24px 0;
  align-items: center;
  box-sizing: border-box;
}
.transverse {
  position: relative;
  width: 80%;
  height: 100px;
  overflow: hidden;
  overflow-x: auto;
  .el-row {
    flex-wrap: nowrap;
  }
}
</style>
```

结尾

自从写了第一篇列表滚动的插件,我就想要多实现几个这种通过vue指令去使用的插件,并把它做成一个开源的工具库。掘友们,有什么意见或者思路欢迎在评论出指出。下一篇会做一个拖拽的指令。

github仓库:github.com/leixq1024/v…

目前仓库什么都没有,只是单纯的把代码上传了,后续会补全readme文件,同时搭建项目示例