vue3 drag and drop hooks (compatible with mobile terminals) and custom instruction drag and drop

Article directory

    • specific goals
    • Overall architecture process
    • Specific code implementation
      • Solution 1 How to write hooks
      • How to use hooks is as follows
      • Option 2: Writing custom instructions
      • The method of customizing instructions is as follows:

Specific goals

1. The drag and drop function is fully functional
2. Do not invade business
3. Boundary values cannot be dragged out of the browser.

Overall architecture process

three steps
Mouse press: Record the position when the mouse is pressed and compare it with the position of the upper left corner of the dragged element to calculate the offset of the mouse press point relative to the dragged element.
Mouse movement: Update the position of the dragged element according to the movement of the mouse pointer, ensuring that the element follows the movement of the mouse.
Mouse up: remove event

Specific code implementation

Plan 1 How to write hooks

//Create useDraggable.ts
export const useDraggable = (): Ref<HTMLDivElement | null> => {<!-- -->
  // Declare a ref to store a reference to the div element
  const divRef = ref<HTMLDivElement | null>(null)
  // Declare some variables to store mouse or touch position and drag state
  let offsetX = 0 //The offset of the mouse click or touch point from the left side of the div
  let offsetY = 0 // The offset of the mouse click or touch point from the top of the div
  let isDragging = false // Whether dragging is in progress
  // Function to disable page scrolling
  const disablePageScroll = () => {<!-- -->
    document.body.style.overflow = 'hidden'
  }
  // Function to enable page scrolling
  const enablePageScroll = () => {<!-- -->
    document.body.style.overflow = 'auto'
  }
  // Start dragging, disable page scrolling
  const startDragging = () => {<!-- -->
    isDragging = true
    disablePageScroll()
  }
  // Stop dragging, enable page scrolling, and later re-enable click events
  const stopDragging = () => {<!-- -->
    isDragging = false
    enablePageScroll()
    setTimeout(() => {<!-- -->
      if (divRef.value) {<!-- -->
        divRef.value.style.pointerEvents = 'auto'
      }
    }, 100)
  }
  // Handle mouse movement or touch movement events
  const handleMouseMove = (event: MouseEvent | TouchEvent) => {<!-- -->
    requestAnimationFrame(() => {<!-- -->
      if (isDragging & amp; & amp; 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
        // Prevent event propagation to avoid interfering with normal scrolling
        event.stopPropagation()
        event.preventDefault()
        // Get the maximum visible area width and height of the browser window
        const maxX = window.innerWidth - (divRef.value.clientWidth || 0)
        const maxY = window.innerHeight - (divRef.value.clientHeight || 0)
        //Set the position of the div to ensure it does not exceed the window range
        divRef.value.style.left = `${<!-- -->Math.min(maxX, Math.max(0, x))}px`
        divRef.value.style.top = `${<!-- -->Math.min(maxY, Math.max(0, y))}px`
        // Disable click events on the div to avoid triggering click events when dragging
        divRef.value.style.pointerEvents = 'none'
      }
    })
  }
  //Handle mouse release or touch end event
  const handleMouseUp = () => {<!-- -->
    // Stop dragging and resume click events
    stopDragging()
    // Remove the listeners for mouse movement events and touch movement events
    document.removeEventListener('touchmove', handleMouseMove)
    document.removeEventListener('mousemove', handleMouseMove)
  }

  //Handle mouse press or touch start event
  const handleMouseDown = (event: MouseEvent | TouchEvent) => {<!-- -->
    if (!divRef.value) return
    // Get the offset of the mouse click or touch point relative to the left and top of the 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
    // Start dragging, add mouse movement and touch movement event listeners
    startDragging()
    document.addEventListener('mousemove', handleMouseMove, {<!-- -->
      passive: false, // Prevent default scrolling behavior
    })
    document.addEventListener('touchmove', handleMouseMove, {<!-- -->
      passive: false, // Prevent default scrolling behavior
    })
    //Add mouse release and touch end event listeners
    document.addEventListener('mouseup', handleMouseUp)
    document.addEventListener('touchend', handleMouseUp)
  }
  // When the component is mounted, add mouse press and touch start event listeners
  onMounted(() => {<!-- -->
    if (divRef.value) {<!-- -->
      divRef.value.addEventListener('mousedown', handleMouseDown)
      divRef.value.addEventListener('touchstart', handleMouseDown)
    }
  })
  // Remove the event listener when the component is unloaded
  onUnmounted(() => {<!-- -->
    if (divRef.value) {<!-- -->
      divRef.value.removeEventListener('mousedown', handleMouseDown)
      divRef.value.removeEventListener('touchstart', handleMouseDown)
    }
    document.removeEventListener('mouseup', handleMouseUp)
    document.removeEventListener('touchend', handleMouseUp)
  })
  // Returns a reference to the div element, which can be used in components to create draggable elements.
  returndivRef
}

How to use hooks is as follows

<template>
  <div
    ref="draggableDiv"
    class="it-layout-aside"
  >Good Good~</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>

Option 2: How to write custom instructions

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 & amp; & amp; (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 = () => {<!-- -->
        //Set the drag state to false
        isDragging = false
        setTimeout(() => {<!-- -->
          el.style.pointerEvents = 'auto'
        }, 100)
        // Remove mouse move and release events
        document.removeEventListener('mousemove', handleMouseMove)
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        document.removeEventListener('mouseup', handleMouseUp)
      }
    })
  },
}

The method of customizing instructions is as follows

<template>
  <div
    v-draggable
    class="it-layout-aside"
  >Are you trendy~</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>