export interface ElementInfo{ el: HTMLElement | null; left: number; top: number; parentLeft: number; parentTop: number; width: number; height: number; // 鼠标点击位置与元素位置的差值 diffX: number; diffY: number; } export interface MouseInfo{ x: number; y: number; } export interface MouseListener{ (mouse: MouseInfo, el: ElementInfo): void; } // 元素拖动 function getElementLeft(el: HTMLElement): number{ let left = el.offsetLeft || 0; if(el.parentElement){ left += getElementLeft(el.parentElement); } return left; } function getElementTop(el: HTMLElement): number{ let top = el.offsetTop || 0; if(el.parentElement){ top += getElementTop(el.parentElement); } return top; } function getElementDistanceToViewportEdge(element: HTMLElement): { top: number, right: number, bottom: number, left: number } { const rect = element.getBoundingClientRect(); const viewportWidth = window.innerWidth || document.documentElement.clientWidth; const viewportHeight = window.innerHeight || document.documentElement.clientHeight; // 兼容性处理 const scrollX = window.scrollX || window.pageXOffset || document.documentElement.scrollLeft; const scrollY = window.scrollY || window.pageYOffset || document.documentElement.scrollTop; const top = rect.top - scrollY; const right = viewportWidth - rect.right + scrollX; const bottom = viewportHeight - rect.bottom + scrollY; const left = rect.left - scrollX; return { top, right, bottom, left }; } // 获取元素滚动条位置 function getScrollLeft(el: HTMLElement): number{ return el.scrollLeft || 0; } function getScrollTop(el: HTMLElement): number{ return el.scrollTop || 0; } /** * 鼠标位置基于滚动条偏移 * @param mouseInfo 直接修改此对象 * @param el */ function comMouseInfo(mouseInfo: MouseInfo, el?: HTMLElement | null): MouseInfo{ // 父元素有滚动条的情况下,需要减去滚动条的偏移量 if (el) { const scrollX = getScrollLeft(el); const scrollY = getScrollTop(el); // console.log(`scrollX: ${scrollX} scrollY: ${scrollY}`); mouseInfo.x += scrollX; mouseInfo.y += scrollY; } return mouseInfo; } export enum CollisionDirection { None = 0, Top = 1, Down = 2, Left = 3, Right = 4 } export interface CollisionResult { colliding: boolean; direction: CollisionDirection; } export interface Rect { x: number; y: number; width: number; height: number; } export function detectCollisionDirection(rect1: Rect, rect2: Rect): CollisionResult { const horizontalOverlap = Math.min(rect1.x + rect1.width, rect2.x + rect2.width) - Math.max(rect1.x, rect2.x); const verticalOverlap = Math.min(rect1.y + rect1.height, rect2.y + rect2.height) - Math.max(rect1.y, rect2.y); if (horizontalOverlap > 0 && verticalOverlap > 0) { if (horizontalOverlap > verticalOverlap) { return { colliding: true, direction: rect1.y < rect2.y ? CollisionDirection.Top : CollisionDirection.Down }; } else { return { colliding: true, direction: rect1.x < rect2.x ? CollisionDirection.Left : CollisionDirection.Right }; } } return { colliding: false, direction: CollisionDirection.None }; } export class Drag{ constructor(el: HTMLElement) { // 绑定事件 this.el = el; this.bindEvent(); } el: HTMLElement; isMove: boolean = false; // 延迟时间, 同一时间内的合并为同一 parent: ElementInfo = { el: null, left: 0, top: 0, parentTop: 0, parentLeft: 0, width: 0, height: 0, diffX: 0, diffY: 0 }; thisInfo: ElementInfo = { el: null, left: 0, top: 0, parentTop: 0, parentLeft: 0, width: 0, height: 0, diffX: 0, diffY: 0 }; startX: number = 0; startY: number = 0; public static Event = { moveStart: "moveStart", move: "move", moveEnd: "mouseEnd" } // private bindEvent() { const el = this.el; el.addEventListener('mousedown', this.downEvent) // todo 触摸支持 let parent = el.parentElement; if (!parent) { parent = document.body; } const parentDistance = getElementDistanceToViewportEdge(parent); this.parent = { el: parent, left: parentDistance.left, top: parentDistance.top, parentTop: 0, parentLeft: 0, width: parent.offsetWidth, height: parent.offsetHeight, diffX: 0, diffY: 0 } this.thisInfo = { el: el, left: getElementLeft(el), top: getElementTop(el), parentTop: this.parent.top, parentLeft: this.parent.left, width: el.offsetWidth, height: el.offsetHeight, diffX: 0, diffY: 0 } } downEvent = (e: MouseEvent) => { if(this.isMove){ return } // 获取鼠标位置 const x = e.clientX; const y = e.clientY; this.isMove = true; // 计算元素偏移差值 const diffX = e.offsetX; const diffY = e.offsetY; let mouseInfo: MouseInfo = { x: x, y: y } this.startX = x; this.startY = y; this.thisInfo = { ...this.thisInfo, diffX: diffX, diffY: diffY } if (this.parent.el) { const parentDistance = getElementDistanceToViewportEdge(this.parent.el) this.thisInfo.parentTop = parentDistance.top this.thisInfo.parentLeft = parentDistance.left comMouseInfo(mouseInfo, this.parent.el); } this.moveStart && this.moveStart(mouseInfo, this.thisInfo); setTimeout(()=>{ document.addEventListener('pointermove', this.mouseMove); }, 100) document.addEventListener('mouseup', this.mouseUp); } mouseUp = (e: MouseEvent) => { this.isMove = false; // 获取鼠标位置 const x = e.clientX; const y = e.clientY; let mouseInfo: MouseInfo = { x: x, y: y } // 解除事件绑定 document.removeEventListener('pointermove', this.mouseMove); document.removeEventListener('mouseup', this.mouseUp); this.moveEnd && this.moveEnd(mouseInfo , this.thisInfo); } mouseMove = (e: MouseEvent) => { if (!this.isMove){ // 已经被取消 document.removeEventListener('pointermove', this.mouseMove); return } // 获取鼠标位置 const x = e.pageX; const y = e.pageY; let mouseInfo: MouseInfo = { x: x, y: y } comMouseInfo(mouseInfo, this.parent.el); this.move && this.move(mouseInfo, this.thisInfo); } moveStart: MouseListener | null = null; move: MouseListener | null = null; moveEnd: MouseListener | null = null; public on(eventName: string, callback: MouseListener) { switch(eventName) { case Drag.Event.moveStart: this.moveStart = callback; break; case Drag.Event.move: this.move = callback; break; case Drag.Event.moveEnd: this.moveEnd = callback; break; default: throw new Error('Invalid event name'); } } public off(eventName: string) { switch(eventName) { case Drag.Event.moveStart: this.moveStart = null; break; case Drag.Event.move: this.move = null; break; case Drag.Event.moveEnd: this.moveEnd = null; break; default: throw new Error('Invalid event name'); } } public destroy() { // 移除事件 this.el.removeEventListener('mousedown', this.downEvent); document.removeEventListener('mouseup', this.mouseUp); document.removeEventListener('mousemove', this.mouseMove); this.off(Drag.Event.moveStart); this.off(Drag.Event.move); this.off(Drag.Event.moveEnd); } }