vue3自定义指令实现全过程

1 阅读1分钟

实现elementplus # Dialog的draggable-拖拽功能

```import { DirectiveBinding, ObjectDirective,nextTick  } from 'vue'

interface DragDirectiveElement extends HTMLElement {
  _dragHandler?: (e: MouseEvent) => void
  _dragUpHandler?: () => void
}

export const dialogDrag: ObjectDirective = {
  async mounted(targetEl: DragDirectiveElement, binding: DirectiveBinding) {
    const el = targetEl.nodeType === 1 ? targetEl : targetEl.$el || targetEl

    await nextTick() // 防止页面没有初始化成功
    const dialogHeaderEl = el.querySelector('.ep-dialog__header') as HTMLElement
    const dragDom = el.querySelector('.ep-dialog') as HTMLElement
    console.log('dialogHeaderEl---',dragDom)
    if (!dialogHeaderEl || !dragDom) return

    dialogHeaderEl.style.cursor = 'move'

    // 获取原有属性
    const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null)

    const onMouseDown = (e: MouseEvent) => {
      // 鼠标按下,计算当前元素距离可视区的距离
      const disX = e.clientX - dialogHeaderEl.offsetLeft
      const disY = e.clientY - dialogHeaderEl.offsetTop

      // 获取到的值带px 正则匹配替换
      let styL: number, styT: number

      // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
      if (sty.left.includes('%')) {
        styL = +document.body.clientWidth * (+sty.left.replace(/%/g, '') / 100)
        styT = +document.body.clientHeight * (+sty.top.replace(/%/g, '') / 100)
      } else {
        styL = +sty.left.replace(/px/g, '')
        styT = +sty.top.replace(/px/g, '')
      }

      const onMouseMove = (e: MouseEvent) => {
        // 通过事件委托,计算移动的距离
        const l = e.clientX - disX
        const t = e.clientY - disY
        
        // 移动当前元素
        dragDom.style.left = `${l + styL}px`
        dragDom.style.top = `${t + styT}px`
        
        // 将此时的位置传出去
        if (binding.value) {
          binding.value({ x: e.pageX, y: e.pageY })
        }
      }

      const onMouseUp = () => {
        document.removeEventListener('mousemove', onMouseMove)
        document.removeEventListener('mouseup', onMouseUp)
      }

      document.addEventListener('mousemove', onMouseMove)
      document.addEventListener('mouseup', onMouseUp)
    }

    dialogHeaderEl.addEventListener('mousedown', onMouseDown)
    el._dragHandler = onMouseDown
  },
  unmounted(el: DragDirectiveElement) {
    const dialogHeaderEl = el.querySelector('.el-dialog__header') as HTMLElement
    if (dialogHeaderEl && el._dragHandler) {
      dialogHeaderEl.removeEventListener('mousedown', el._dragHandler)
    }
  }
}

main.ts

// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import { directives } from './directives'

const app = createApp(App)

// 批量注册指令
Object.entries(directives).forEach(([name, directive]) => {
  app.directive(name, directive)
})

app.mount('#app')

在组件中直接使用

<template>
  <!-- 使用 v-drag 指令 -->
  <el-dialog v-drag />
</template>