/** * 支持父级,自定义父级,以及window作为父级 * 使用示例: * 1. v-dragable * 2. v-dragable="'father'" // 使用父级作为父级 * 3. v-dragable="'body'" // 使用body对象作为父级 * 4. v-dragable="'#app'" // 使用id作为父级 * 5. v-dragable="'.list'" // 使用class名作为父级 * 3-5代表所有可被document.querySelector()解析的参数值 **/ import type { Directive, DirectiveBinding } from 'vue' interface Position { x: number, y: number } interface Mouse { down: Position, move: Position } interface ElType extends HTMLElement { __mouseDown__: any, __mouseUp__: any, __mouseMove__: any, __parentDom__: HTMLElement, __position__: Position } const directive: Directive = { mounted: (el: ElType, binding: DirectiveBinding) => { setParentDom(el, binding, false) // 子级元素位置处理 // 1. 获取父子元素当前位置 let parentDomRect: DOMRect let elDomRect: DOMRect const mouseData: Mouse = { down: { x: 0, y: 0}, move: { x: 0, y: 0 } } let mouseDown = false el.__position__ = { x: 0, y: 0 } let bodyUserSelect = 'text' function handleMouseDown(e: MouseEvent) { if (e.button !== 0) { return } mouseData.down = { x: e.clientX, y: e.clientY } mouseDown = true parentDomRect = el.__parentDom__.getBoundingClientRect() elDomRect = el.getBoundingClientRect() bodyUserSelect = document.querySelector('body')!.style.userSelect document.querySelector('body')!.style.userSelect = "none" } function handleMouseMove(e: MouseEvent) { if (!mouseDown) { return } mouseData.move = { x: e.clientX, y: e.clientY } setPosition() } function handleMouseUp(e: MouseEvent) { if (mouseDown) { mouseDown = false document.querySelector('body')!.style.userSelect = bodyUserSelect } } // 用于设置el元素的Position位置 function setPosition() { // 通过几何图形计算更佳,我就是通过几何画图计算出来的当前数据,使用者可以自行计算,得到这两个值 const x = mouseData.move.x + elDomRect.x - parentDomRect.x - mouseData.down.x const y = mouseData.move.y + elDomRect.y - parentDomRect.y - mouseData.down.y // 进行x,y坐标边界处理判断 if (x < 0) { el.__position__.x = 0 } else if (x > parentDomRect.width - elDomRect.width) { el.__position__.x = parentDomRect.width - elDomRect.width } else { el.__position__.x = x } if (y < 0) { el.__position__.y = 0 } else if (y > parentDomRect.height - elDomRect.height) { el.__position__.y = parentDomRect.height - elDomRect.height } else { el.__position__.y = y } // 渲染到真实dom属性上 el.style.cssText += ` position: absolute; z-index: 100; left: ${ el.__position__.x }px; top: ${ el.__position__.y }px; ` } el.__mouseDown__ = handleMouseDown el.__mouseMove__ = handleMouseMove el.__mouseUp__ = handleMouseUp // 2. 监听拖拽事件 el.addEventListener('mousedown', el.__mouseDown__) document.addEventListener('mousemove', el.__mouseMove__) document.addEventListener('mouseup', el.__mouseUp__) }, updated(el, binding) { setParentDom(el, binding, true) }, beforeUnmount(el: ElType) { // 避免重复开销,卸载所有的监听 // 解决问题:多次创建新的实例 =》 监听不取消 =》 同时存在多个无用的监听,导致页面性能变差 document.removeEventListener('mousedown', el.__mouseDown__) document.removeEventListener('mousemove', el.__mouseMove__) document.removeEventListener('mouseup', el.__mouseUp__) } } // 设置parentDom,供mounted和update使用 function setParentDom(el: ElType, binding: DirectiveBinding, updated: boolean) { const array = [ { name: 'father', dom: el.parentElement } ] // 获取父级元素 let parentDom: HTMLElement | HTMLBodyElement // 以下if操作用于确保一定有一个parentDom if (binding.value) { const findArr = array.find((arr) => { return arr.name === binding.value }) if (findArr && findArr.dom) { parentDom = findArr.dom } else { parentDom = document.querySelector(binding.value) || array[0].dom as HTMLElement || array[1].dom } } else { parentDom = array[0].dom as HTMLElement || array[1].dom } const parentDomRect = parentDom.getBoundingClientRect() const elDomRect = el.getBoundingClientRect() // 把以前的样式重置一下 if (el.__parentDom__) { el.__parentDom__.style.position = 'static' } el.__parentDom__ = parentDom el.__parentDom__.style.position = 'relative' if (updated) { el.__position__ = { x: elDomRect.x - parentDomRect.x, y: elDomRect.y - parentDomRect.y } // return el.style.cssText += ` position: absolute; z-index: 100; left: ${ el.__position__.x }px; top: ${ el.__position__.y }px; ` } } export default directive