Files
shanghai-residences/src/hooks/useDragAndDrop.ts
T

149 lines
3.8 KiB
TypeScript

import { useState, useEffect, useCallback, useRef } from "react";
import { useScale } from "@/contexts/ScaleContext";
interface DragState<T = any> {
isDragging: boolean;
dragData: T | null;
mousePosition: { x: number; y: number };
}
/**
* 自定义拖拽 Hook
* 支持页面 scale 缩放的拖拽功能
*/
export function useDragAndDrop<T = any>() {
const { scale, offsetX, offsetY } = useScale();
const [state, setState] = useState<DragState<T>>({
isDragging: false,
dragData: null,
mousePosition: { x: 0, y: 0 },
});
const isDraggingRef = useRef(false);
// 开始拖拽
const startDrag = useCallback((data: T, event?: React.MouseEvent) => {
if (event) {
event.preventDefault();
event.stopPropagation();
// 立即获取鼠标初始位置
isDraggingRef.current = true;
setState({
isDragging: true,
dragData: data,
mousePosition: { x: event.clientX, y: event.clientY },
});
} else {
isDraggingRef.current = true;
setState({
isDragging: true,
dragData: data,
mousePosition: { x: 0, y: 0 },
});
}
}, []);
// 结束拖拽
const endDrag = useCallback(() => {
isDraggingRef.current = false;
setState({
isDragging: false,
dragData: null,
mousePosition: { x: 0, y: 0 },
});
}, []);
// 监听鼠标移动 - 使用独立的事件监听器
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
if (!isDraggingRef.current) return;
setState((prev) => ({
...prev,
mousePosition: { x: e.clientX, y: e.clientY },
}));
};
const handleMouseUp = () => {
// 拖拽结束的逻辑由外部处理
// 这里只是提供一个安全机制
};
// 始终添加事件监听器,在handleMouseMove内部检查拖拽状态
window.addEventListener("mousemove", handleMouseMove);
window.addEventListener("mouseup", handleMouseUp);
return () => {
window.removeEventListener("mousemove", handleMouseMove);
window.removeEventListener("mouseup", handleMouseUp);
};
}, []); // 空依赖数组,只在组件挂载/卸载时执行
// 将屏幕坐标转换为缩放后的页面坐标
const screenToScaledCoords = useCallback(
(screenX: number, screenY: number) => {
return {
x: (screenX - offsetX) / scale,
y: (screenY - offsetY) / scale,
};
},
[scale, offsetX, offsetY]
);
// 检查点是否在元素内
const isPointInElement = useCallback(
(pointX: number, pointY: number, element: HTMLElement | null): boolean => {
if (!element) return false;
const rect = element.getBoundingClientRect();
return (
pointX >= rect.left &&
pointX <= rect.right &&
pointY >= rect.top &&
pointY <= rect.bottom
);
},
[]
);
return {
isDragging: state.isDragging,
dragData: state.dragData,
mousePosition: state.mousePosition,
startDrag,
endDrag,
screenToScaledCoords,
isPointInElement,
};
}
/**
* 放置区域 Hook
* 用于检测拖拽元素是否悬停在放置区域上
*/
export function useDropZone() {
const dropZoneRef = useRef<HTMLDivElement>(null);
const [isOver, setIsOver] = useState(false);
// 检查鼠标是否在放置区域内
const checkIsOver = useCallback((mouseX: number, mouseY: number) => {
if (!dropZoneRef.current) return false;
const rect = dropZoneRef.current.getBoundingClientRect();
const over =
mouseX >= rect.left &&
mouseX <= rect.right &&
mouseY >= rect.top &&
mouseY <= rect.bottom;
// 只有当状态真正改变时才更新
setIsOver((prev) => (prev !== over ? over : prev));
return over;
}, []);
return {
dropZoneRef,
isOver,
checkIsOver,
};
}