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.

Revisions

  1. HereOrCode revised this gist Feb 3, 2024. 1 changed file with 6 additions and 0 deletions.
    6 changes: 6 additions & 0 deletions useDrag.ts
    Original file line number Diff line number Diff line change
    @@ -151,3 +151,9 @@ export const useDrag = ({
    recalculate,
    };
    };

    /**
    * How to use?
    * Detail to see:
    * https://stackblitz-starters-51mnfq.stackblitz.io
    */
  2. HereOrCode created this gist Feb 3, 2024.
    153 changes: 153 additions & 0 deletions useDrag.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,153 @@
    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,
    };
    };