WholeProcessPlatform/frontend/src/utils/drag.ts

183 lines
5.6 KiB
TypeScript

import { nextTick, watch } from 'vue';
export interface DraggableOptions {
/** 拖拽手柄选择器,默认 '.ant-modal-header' */
handle?: string;
/** 弹窗内容选择器,默认 '.ant-modal-content' */
modalSelector?: string;
/** 是否限制在可视区域内,默认 true */
boundary?: boolean;
/** 打开弹窗时是否自动重置到居中位置,默认 true */
resetOnOpen?: boolean;
}
export function useDraggable(
openRef: { value: boolean },
options: DraggableOptions = {}
) {
console.log(openRef);
const {
handle = '.ant-modal-header',
modalSelector = '.ant-modal-content',
boundary = true,
resetOnOpen = true
} = options;
let cleanup: (() => void) | null = null;
const initDrag = (): (() => void) | null => {
if (resetOnOpen) {
const modalContents = document.querySelectorAll(
modalSelector
) as NodeListOf<HTMLElement>;
modalContents.forEach(content => {
if (content.offsetParent !== null) {
content.style.position = '';
content.style.left = '';
content.style.top = '';
content.style.margin = '';
content.style.transform = '';
content.style.width = '';
}
});
}
let currentModalContent: HTMLElement | null = null;
let isDragging = false;
let startX = 0;
let startY = 0;
let startLeft = 0;
let startTop = 0;
const getPointerPos = (e: MouseEvent | TouchEvent) => {
if (e instanceof TouchEvent) {
const touch = e.touches[0];
return { clientX: touch.clientX, clientY: touch.clientY };
}
return { clientX: e.clientX, clientY: e.clientY };
};
const onPointerDown = (e: MouseEvent | TouchEvent) => {
const target = e.target instanceof HTMLElement ? e.target : null;
const dragHandle = target?.closest(handle) as HTMLElement | null;
if (!dragHandle) return;
const modalContent = dragHandle.closest(
modalSelector
) as HTMLElement | null;
if (!modalContent) return;
if (
target.closest('.ant-modal-close') ||
target.closest('button') ||
target.closest('input') ||
target.closest('select') ||
target.closest('textarea') ||
target.closest('a')
)
return;
e.preventDefault();
isDragging = true;
currentModalContent = modalContent;
const rect = modalContent.getBoundingClientRect();
startLeft = rect.left;
startTop = rect.top;
const pos = getPointerPos(e);
startX = pos.clientX - startLeft;
startY = pos.clientY - startTop;
const currentWidth = modalContent.offsetWidth;
modalContent.style.width = currentWidth + 'px';
modalContent.style.userSelect = 'none';
modalContent.style.webkitUserSelect = 'none';
modalContent.style.position = 'fixed';
modalContent.style.margin = '0';
modalContent.style.transform = 'none';
document.addEventListener('mousemove', onPointerMove, { passive: false });
document.addEventListener('mouseup', onPointerUp);
document.addEventListener('touchmove', onPointerMove, { passive: false });
document.addEventListener('touchend', onPointerUp);
};
const onPointerMove = (e: MouseEvent | TouchEvent) => {
if (!isDragging || !currentModalContent) return;
e.preventDefault();
const pos = getPointerPos(e);
let newLeft = pos.clientX - startX;
let newTop = pos.clientY - startY;
if (boundary) {
const maxX = window.innerWidth - currentModalContent.offsetWidth;
const maxY = window.innerHeight - currentModalContent.offsetHeight;
newLeft = Math.max(0, Math.min(newLeft, maxX));
newTop = Math.max(0, Math.min(newTop, maxY));
}
currentModalContent.style.left = `${newLeft}px`;
currentModalContent.style.top = `${newTop}px`;
};
const onPointerUp = () => {
if (!currentModalContent) return;
isDragging = false;
currentModalContent.style.userSelect = '';
currentModalContent.style.webkitUserSelect = '';
document.removeEventListener('mousemove', onPointerMove);
document.removeEventListener('mouseup', onPointerUp);
document.removeEventListener('touchmove', onPointerMove);
document.removeEventListener('touchend', onPointerUp);
currentModalContent = null;
};
document.addEventListener('mousedown', onPointerDown);
document.addEventListener('touchstart', onPointerDown, {
passive: false
});
return () => {
document.removeEventListener('mousedown', onPointerDown);
document.removeEventListener('touchstart', onPointerDown);
document.removeEventListener('mousemove', onPointerMove);
document.removeEventListener('mouseup', onPointerUp);
document.removeEventListener('touchmove', onPointerMove);
document.removeEventListener('touchend', onPointerUp);
const modalContents = document.querySelectorAll(
modalSelector
) as NodeListOf<HTMLElement>;
modalContents.forEach(content => {
content.style.position = '';
content.style.left = '';
content.style.top = '';
content.style.margin = '';
content.style.transform = '';
content.style.width = '';
content.style.userSelect = '';
content.style.webkitUserSelect = '';
});
};
};
watch(
openRef,
newVal => {
if (newVal) {
nextTick(() => {
cleanup = initDrag();
});
} else {
cleanup?.();
cleanup = null;
}
},
{ immediate: true }
);
return { initDrag };
}