vue 拖拽hooks ts版

2021-01-27 09:53:40 浏览数 (1)

代码语言:javascript复制
import {
  ref,
} from 'vue'
import { useBool } from './useBool'

export interface AnyFunction {
  (...args: any): any
}

// 数据获取标识
const DATA_SIGN = 'DRAG'

export function getDragDataSign() {
  return DATA_SIGN
}

/**
 * DOM 绑定拖拽事件
 * @returns { { elems, handlers, bindEle } } 
 * - elems Ref 已绑定元素列表
 * - handlers Ref 数据绑定函数
 * - bindEle any => element => void 接收事件传参,返回dom元素收集器
 * @example
 * const { bindEle } = useDrag()
 * const element = document.querySelector('.move-block')
 * element.onDragstart = bindDragstart({id: 1})
 */
export function useDrag(dataSign = DATA_SIGN) {
  const bindDragstart = <T>(data: T) => (e: DragEvent) => {
    /* eslint-disable-next-line no-unused-expressions */
    e.dataTransfer?.setData(dataSign, JSON.stringify(data))
  }

  return { bindDragstart }
}

export interface DragEvents {
  dragover?: (e: DragEvent) => void
  dragenter?: (e: DragEvent) => void
  dragleave?: (e: DragEvent) => void
  dragstart?: (e: DragEvent) => void
  dragend?: (e: DragEvent) => void
  drop?: (e: DragEvent) => void
  paste?: (e: DragEvent) => void
}


export interface DragCallbackType {
  onDom?(domStr: string, e: DragEvent): void
  onUri?(url: string, e: DragEvent): void
  onFiles?(files: DataTransfer['files'], e: DragEvent): void
  onText?(text: string, e: DragEvent): void
  onAny?(e: DragEvent): void
}

// 函数存在既运行
function hasAndRun(fn?: (...args: any) => any, ...args: any[]) {
  if (fn) {
    fn(...args)
  }
}

/**
 * 拖拽区 hook
 * @param { Object } options 拖拽响应回调, 用于处理不同类型数据
 * - onDom dom拖拽释放回调
 * - onUri uri拖拽释放回调
 * - onFiles file拖拽释放回调
 * - onText text拖拽释放回调
 * 
 * @param { Object } events 自定义拖拽事件
 * 
 * @param { string } dataSign 拖拽取值标识
 * 
 * @returns { array  } 
 * - props 拖拽监听函数
 * - isHovering 是否进入监听区
 * 
 * @example
 * --- js
 * const dropType = {
 *   onDom(data){
 *    console.log()
 *   }
 * }
 * const { dragEvents } = useDragArea(dropType)
 * 
 * --- html
 * <div class='area' v-on='dragEvents'>
 * </div>
 */
export function useDragArea(
  dropType = {} as DragCallbackType,
  events = {} as DragEvents,
  dataSign = DATA_SIGN
) {
  const optionsRef = ref(dropType)
  const { state: isHovering, setTrue: startHover, setFalse: endHover } = useBool()
  const { state: isRun, setTrue: startRun, setFalse: endRun } = useBool()

  const callback = (dataTransfer: DataTransfer | null, event: DragEvent) => {
    if (dataTransfer === null) {
      return
    }

    const url = dataTransfer.getData(dataSign)
    const dom = dataTransfer.getData(dataSign)

    const {
      onDom,
      onUri,
      onFiles,
      onText,
      onAny
    } = optionsRef.value

    if (dom && onDom) {
      onDom(JSON.parse(dom), event)
      return
    }

    if (url && onUri) {
      onUri(url, event)
      return
    }

    if (dataTransfer.files && dataTransfer.files.length && onFiles) {
      onFiles(dataTransfer.files, event)
      return
    }

    if (dataTransfer.items && dataTransfer.items.length && onText) {
      dataTransfer.items[0].getAsString((text) => { onText(text, event) })
      return
    }

    if (onAny) {
      onAny(event)
    }
  }

  const {
    dragover,
    dragenter,
    dragleave,
    dragstart,
    dragend,
    paste,
    drop
  } = events

  const dragEvents = {
    dragover: (e: DragEvent) => {
      e.preventDefault()
      e.stopPropagation()
      hasAndRun(dragover, e)
    },
    dragenter: (e: DragEvent) => { 
      e.preventDefault()
      e.stopPropagation()
      startHover()
      hasAndRun(dragenter, e)
    },
    dragleave: (e: DragEvent) => { 
      e.preventDefault()
      e.stopPropagation()
      endHover()
      hasAndRun(dragleave, e)
    },
    dragstart: (e: DragEvent) => { 
      e.preventDefault()
      e.stopPropagation()
      startRun()
      hasAndRun(dragstart, e)
    },
    dragend: (e: DragEvent) => { 
      e.preventDefault()
      e.stopPropagation()
      endRun()
      hasAndRun(dragend, e)
    },
    paste: (e: DragEvent) => {
      callback(e.dataTransfer, e)
      hasAndRun(paste, e)
    },
    drop: (e: DragEvent) => {
      e.preventDefault()
      e.stopPropagation()
      endHover()
      callback(e.dataTransfer, e)
      hasAndRun(drop, e)
    }
  }

  return {
    dragEvents,
    isHovering,
    isRun
  }
}

注意点

  • 拖拽属性必须明确设置为 draggable='true' 短写模式无效 dragg
  • 需配置 dragover 事件,否则拖拽区内将出现禁止提示
  • 事件设置 e.preventDefault
  • 事件设置 e.stopPropagation 防止嵌套拖拽时,触发父元素事件

0 人点赞