Skip to content

Instantly share code, notes, and snippets.

@HereOrCode
Last active February 3, 2024 07:45
Show Gist options
  • Save HereOrCode/8f43ff8d77dad8fce4e6f6c947505bf1 to your computer and use it in GitHub Desktop.
Save HereOrCode/8f43ff8d77dad8fce4e6f6c947505bf1 to your computer and use it in GitHub Desktop.
React Hook: Draggable div element
import { useCallback, useEffect, useState } from "react";
type DragInfo = {
startX: number;
startY: number;
top: number;
left: number;
width: number;
height: number;
};
type Position = {
x: number;
y: number;
};
interface DragProps {
ref: React.RefObject<HTMLElement>;
position: Position;
calculateFor?: "topLeft" | "bottomRight";
}
export const useDrag = ({
ref,
position,
calculateFor = "topLeft",
}: DragProps) => {
const [dragInfo, setDragInfo] = useState<DragInfo>();
const [finalPosition, setFinalPosition] = useState<Position>({
x: position.x,
y: position.y,
});
const [isDragging, setIsDragging] = useState(false);
const updateFinalPosition = useCallback(
(width: number, height: number, x: number, y: number) => {
if (calculateFor === "bottomRight") {
setFinalPosition({
x: Math.max(
Math.min(
window.innerWidth - width,
window.innerWidth - (x + width)
),
0
),
y: Math.max(
Math.min(
window.innerHeight - height,
window.innerHeight - (y + height)
),
0
),
});
return;
}
setFinalPosition({
x: Math.min(Math.max(0, x), window.innerWidth - width),
y: Math.min(Math.max(0, y), window.innerHeight - height),
});
},
[calculateFor]
);
const handleMouseUp = (event: MouseEvent) => {
event.preventDefault();
setIsDragging(false);
};
const handleMouseDown = (event: MouseEvent) => {
event.preventDefault();
const { clientX, clientY } = event;
const { current: draggableElement } = ref;
if (!draggableElement) {
return;
}
const { top, left, width, height } =
draggableElement.getBoundingClientRect();
setIsDragging(true);
setDragInfo({
startX: clientX,
startY: clientY,
top,
left,
width,
height,
});
};
const handleMouseMove = useCallback(
(event: MouseEvent) => {
const { current: draggableElement } = ref;
if (!isDragging || !draggableElement || !dragInfo) return;
event.preventDefault();
const { clientX, clientY } = event;
const position = {
x: dragInfo.startX - clientX,
y: dragInfo.startY - clientY,
};
const { top, left, width, height } = dragInfo;
updateFinalPosition(width, height, left - position.x, top - position.y);
},
[isDragging, dragInfo, ref, updateFinalPosition]
);
const recalculate = (width: number, height: number) => {
const { current: draggableElement } = ref;
if (!draggableElement) return;
const {
top,
left,
width: boundingWidth,
height: boundingHeight,
} = draggableElement.getBoundingClientRect();
updateFinalPosition(
width ?? boundingWidth,
height ?? boundingHeight,
left,
top
);
};
useEffect(() => {
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
return () => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
}, [handleMouseMove]);
return {
position: finalPosition,
handleMouseDown,
recalculate,
};
};
/**
* How to use?
* Detail to see:
* https://stackblitz-starters-51mnfq.stackblitz.io
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment