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>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Vue.js是一种流行的JavaScript框架,用于构建用户界面。它提供了一种简洁的方式来处理数据和DOM的交互。在Vue.js中,自定义指令是一种扩展Vue.js功能的方式,可以用于在DOM元素上添加特定的行为。 在Vue.js 2中,自定义指令通过`Vue.directive`方法来定义。自定义指令可以用于操作DOM元素、监听事件、修改样式等。例如,我们可以创建一个自定义指令来实现点击元素时改变背景颜色的功能: ```javascript Vue.directive('change-color', { bind: function (el, binding) { el.addEventListener('click', function () { el.style.backgroundColor = binding.value; }); } }); ``` 在上面的例子中,我们使用`bind`钩子函数来绑定指令,并在元素上添加点击事件监听器。当点击元素时,会根据指令的值来改变元素的背景颜色。 而在Vue.js 3中,自定义指令的写法有所改变。Vue.js 3引入了`createApp`方法来创建应用程序实例,并使用`app.directive`方法来定义自定义指令。例如,我们可以使用Vue.js 3的语法来重新实现上述的自定义指令: ```javascript const app = Vue.createApp({}); app.directive('change-color', { mounted(el, binding) { el.addEventListener('click', function () { el.style.backgroundColor = binding.value; }); } }); app.mount('#app'); ``` 在上面的例子中,我们使用`mounted`钩子函数来绑定指令,并在元素上添加点击事件监听器。当点击元素时,会根据指令的值来改变元素的背景颜色。 总结一下,Vue.js 2和Vue.js 3中的自定义指令的定义方式有所不同,但都可以用于扩展Vue.js的功能,实现特定的行为。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值