183 lines
5.6 KiB
TypeScript
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 };
|
|
}
|