reactjsreact-hooksdom-events

Resize react element with hook


I'm trying to create a hook that allows me to resize an element. I've used this answer as a point of reference and created the following.

The resize works, but not accurately. The resize handle does not follow the cursor and it moves somewhat sporadically.

Can anyone please help me understand what the problems are with this hook?

Codepen

Preview: enter image description here

The hook:

interface Dimensions {
  width: number | null;
  height: number | null;
}

interface UseResizableProps {
  elementRef: React.RefObject<HTMLDivElement>;
  handleRef: React.RefObject<HTMLDivElement>;
}

function useResizable({ elementRef, handleRef }: UseResizableProps) {
  const dimensions = useRef<Dimensions>({ width: null, height: null });

  // Since we dont know the size that the element should initialize to,
  // we need to grab the sizes from the element rect
  useEffect(() => {
    if (elementRef.current) {
      const rect = elementRef.current.getBoundingClientRect();
      dimensions.current = { height: rect.height, width: rect.width };
    }
  }, [elementRef]);

  useEffect(() => {
    function handleMouseDownE(mouseDownEvent: MouseEvent) {
      const startPosition = {
        x: mouseDownEvent.pageX,
        y: mouseDownEvent.pageY,
      };
      function onMouseMove(mouseMoveEvent: MouseEvent) {
        if (
          dimensions.current.height === null ||
          dimensions.current.width === null
        ) {
          return;
        }
        dimensions.current = {
          width:
            dimensions.current.width - startPosition.x + mouseMoveEvent.pageX,
          height: dimensions.current.height,
        };

        if (
          elementRef?.current?.style.width !== undefined &&
          dimensions.current.width
        ) {
          const px = `${dimensions.current.width}px`;
          elementRef.current.style.width = px;
        }
      }
      function onMouseUp() {
        document.body.removeEventListener("mousemove", onMouseMove);
      }
      document.body?.addEventListener("mousemove", onMouseMove);
      document.body?.addEventListener("mouseup", onMouseUp, { once: true });
    }
    handleRef?.current?.addEventListener("mousedown", handleMouseDownE);
  });
}

Hook usage:

function App() {
  const elementRef = useRef<HTMLDivElement>(null);
  const handleRef = useRef<HTMLDivElement>(null);

  useResizable({ elementRef, handleRef });

  return (
    <div className="App">
      <div className="resizable" ref={elementRef}>
        <div className="resize-handle" ref={handleRef}></div>
      </div>
    </div>
  );
}

Styles:

.resizable {
  width: 500px;
  height: 500px;
  background-color: lightpink;
  position: relative;
}

.resize-handle {
  width: 5px;
  height: 100%;
  background-color: orangered;
  right: 0;
  position: absolute;
  cursor: e-resize;
}


Solution

  • The issue is with the width calculation. You are referring to the currentDimensions as the dimensions when the mouseDown event first fired, then modifying this when the mouseMouse even fires. Solution was to take a copy of the dimensions when the mouseDown event fires, e.g. From:

        function handleMouseDownE(mouseDownEvent: MouseEvent) {
          const startPosition = {
            x: mouseDownEvent.pageX,
            y: mouseDownEvent.pageY,
          };
          function onMouseMove(mouseMoveEvent: MouseEvent) {
            if (
              dimensions.current.height === null ||
              dimensions.current.width === null
            ) {
              return;
            }
            dimensions.current = {
              width:
                dimensions.current.width - startPosition.x + mouseMoveEvent.pageX,
              height: dimensions.current.height,
            };
    
            if (
              elementRef?.current?.style.width !== undefined &&
              dimensions.current.width
            ) {
              const px = `${dimensions.current.width}px`;
              elementRef.current.style.width = px;
            }
          }
    

    To:

        function handleMouseDownE(mouseDownEvent: MouseEvent) {
          const startDimensions = { ...dimensions.current }; // <<<< Add this
          const startPosition = {
            x: mouseDownEvent.pageX,
            y: mouseDownEvent.pageY,
          };
          function onMouseMove(mouseMoveEvent: MouseEvent) {
            if (
              dimensions.current.height === null ||
              dimensions.current.width === null
            ) {
              return;
            }
            dimensions.current = {
              width:
                startDimensions.width - startPosition.x + mouseMoveEvent.pageX, // <<<< Add this
              height: startDimensions.height,
            };
    
            if (
              elementRef?.current?.style.width !== undefined &&
              dimensions.current.width
            ) {
              const px = `${dimensions.current.width}px`;
              elementRef.current.style.width = px;
            }
          }