vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > vue3 拖拽hooks

vue3 拖拽hooks(可兼容移动端)和自定义指令拖拽的实现代码

作者:暮冬十七

这篇文章主要介绍了vue3 拖拽hooks(可兼容移动端)和自定义指令拖拽的实现代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考借鉴价值,需要的朋友参考下吧

具体目标

1、拖拽功能完好使用
2、不入侵业务
3、边界值比如不能拖拽出浏览器外

整体架构流程

三步走
鼠标按下:将鼠标按下时的位置记录并与被拖拽元素的左上角位置进行比较,以计算出鼠标按下点相对于被拖拽元素的偏移,
鼠标移动:根据鼠标指针的移动来更新被拖拽元素的位置,确保元素跟随鼠标的移动。
鼠标抬起:移除事件

具体代码实现

方案一 hooks写法

// 创建useDraggable.ts
export const useDraggable = (): Ref<HTMLDivElement | null> => {
  // 声明一个 ref,用于存储 div 元素的引用
  const divRef = ref<HTMLDivElement | null>(null)
  // 声明一些变量,用于存储鼠标或触摸位置以及拖拽状态
  let offsetX = 0 // 鼠标点击或触摸点距离 div 左侧的偏移
  let offsetY = 0 // 鼠标点击或触摸点距离 div 顶部的偏移
  let isDragging = false // 是否正在拖拽中
  // 禁用页面滚动的函数
  const disablePageScroll = () => {
    document.body.style.overflow = 'hidden'
  }
  // 启用页面滚动的函数
  const enablePageScroll = () => {
    document.body.style.overflow = 'auto'
  }
  // 开始拖拽,禁用页面滚动
  const startDragging = () => {
    isDragging = true
    disablePageScroll()
  }
  // 停止拖拽,启用页面滚动,并稍后重新启用点击事件
  const stopDragging = () => {
    isDragging = false
    enablePageScroll()
    setTimeout(() => {
      if (divRef.value) {
        divRef.value.style.pointerEvents = 'auto'
      }
    }, 100)
  }
  // 处理鼠标移动或触摸移动事件
  const handleMouseMove = (event: MouseEvent | TouchEvent) => {
    requestAnimationFrame(() => {
      if (isDragging && divRef.value) {
        const clientX = 'touches' in event ? event.touches[0].clientX : event.clientX
        const clientY = 'touches' in event ? event.touches[0].clientY : event.clientY
        const x = clientX - offsetX
        const y = clientY - offsetY
        // 阻止事件传播,避免干扰正常滚动
        event.stopPropagation()
        event.preventDefault()
        // 获取浏览器窗口的最大可视区域宽度和高度
        const maxX = window.innerWidth - (divRef.value.clientWidth || 0)
        const maxY = window.innerHeight - (divRef.value.clientHeight || 0)
        // 设置 div 的位置,确保不超出窗口范围
        divRef.value.style.left = `${Math.min(maxX, Math.max(0, x))}px`
        divRef.value.style.top = `${Math.min(maxY, Math.max(0, y))}px`
        // 禁用 div 上的点击事件,以避免拖拽时触发点击事件
        divRef.value.style.pointerEvents = 'none'
      }
    })
  }
  // 处理鼠标松开或触摸结束事件
  const handleMouseUp = () => {
    // 停止拖拽,恢复点击事件
    stopDragging()
    // 移除鼠标移动事件和触摸移动事件的监听器
    document.removeEventListener('touchmove', handleMouseMove)
    document.removeEventListener('mousemove', handleMouseMove)
  }
  // 处理鼠标按下或触摸开始事件
  const handleMouseDown = (event: MouseEvent | TouchEvent) => {
    if (!divRef.value) return
    // 获取鼠标点击或触摸点相对于 div 左侧和顶部的偏移
    offsetX = 'touches' in event ? event.touches[0].clientX - divRef.value.offsetLeft : event.clientX - divRef.value.offsetLeft
    offsetY = 'touches' in event ? event.touches[0].clientY - divRef.value.offsetTop : event.clientY - divRef.value.offsetTop
    // 开始拖拽,添加鼠标移动和触摸移动事件监听器
    startDragging()
    document.addEventListener('mousemove', handleMouseMove, {
      passive: false, // 阻止默认滚动行为
    })
    document.addEventListener('touchmove', handleMouseMove, {
      passive: false, // 阻止默认滚动行为
    })
    // 添加鼠标松开和触摸结束事件监听器
    document.addEventListener('mouseup', handleMouseUp)
    document.addEventListener('touchend', handleMouseUp)
  }
  // 在组件挂载时,添加鼠标按下和触摸开始事件监听器
  onMounted(() => {
    if (divRef.value) {
      divRef.value.addEventListener('mousedown', handleMouseDown)
      divRef.value.addEventListener('touchstart', handleMouseDown)
    }
  })
  // 在组件卸载时,移除事件监听器
  onUnmounted(() => {
    if (divRef.value) {
      divRef.value.removeEventListener('mousedown', handleMouseDown)
      divRef.value.removeEventListener('touchstart', handleMouseDown)
    }
    document.removeEventListener('mouseup', handleMouseUp)
    document.removeEventListener('touchend', handleMouseUp)
  })
  // 返回 div 元素的引用,可以在组件中使用该引用来创建可拖拽的元素
  return divRef
}

hooks的使用方法如下

<template>
  <div
    ref="draggableDiv"
    class="it-layout-aside"
  >古德古德~</div>
</template>
<script setup lang="tsx">
  import { useDraggable } from '~/hooks/useDraggable'
  const draggableDiv = useDraggable()
</script>
<style lang="stylus" scoped>
.it-layout-aside
  flexCenter()
  position fixed
  bottom 100px
  right 10px
  width 60px
  height 60px
  border-radius 50%
  background rgba(0,0,0,.5)
  opacity 0.8
  color #fff
  font-size 40px
  cursor move
  &:hover
    opacity 1
</style>

方案二 自定义指令写法

const vDraggable = {
  mounted(el: HTMLElement) {
    let offsetX = 0
    let offsetY = 0
    let isDragging = false
    el.addEventListener('mousedown', event => {
      isDragging = false
      offsetX = event.clientX - el.offsetLeft
      offsetY = event.clientY - el.offsetTop
      const handleMouseMove = (e: MouseEvent) => {
        if (!isDragging && (Math.abs(event.clientX - offsetX) > 5 || Math.abs(event.clientY - offsetY) > 5)) {
          isDragging = true
        }
        if (isDragging) {
          const x = e.clientX - offsetX
          const y = e.clientY - offsetY
          el.style.left = `${Math.min(window.innerWidth - el.clientWidth, Math.max(0, x))}px`
          el.style.top = `${Math.min(window.innerHeight - el.clientHeight, Math.max(0, y))}px`
          el.style.pointerEvents = 'none'
        }
      }
      const handleMouseUp = () => {
        // 设置拖动状态为false
        isDragging = false
        setTimeout(() => {
          el.style.pointerEvents = 'auto'
        }, 100)
        // 移除鼠标移动和松开事件
        document.removeEventListener('mousemove', handleMouseMove)
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        document.removeEventListener('mouseup', handleMouseUp)
      }
    })
  },
}

自定义指令的方法使用如下

<template>
  <div
    v-draggable
    class="it-layout-aside"
  >你潮嘛~</div>
</template>
<style lang="stylus" scoped>
.it-layout-aside
  flexCenter()
  position fixed
  bottom 100px
  right 10px
  width 60px
  height 60px
  border-radius 50%
  background rgba(0,0,0,.5)
  opacity 0.8
  color #fff
  font-size 40px
  cursor move
  &:hover
    opacity 1
</style>

到此这篇关于vue3 拖拽hooks(可兼容移动端)和自定义指令拖拽的文章就介绍到这了,更多相关vue3 拖拽hooks内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文