'React - Drag corner of element to resize contents

I'm working on a feature in my React project where when a user hovers over an element, which could contain an image or just text, a resize button appears in the top left corner and pressing down on that button while dragging the mouse will resize the element and it's contents accordingly.

I've already implemented showing the resize button on hover, but am having difficulty implementing the resizing feature.

For reference, I've attached a GIF of what I'm trying to implement. Resizing an Element



Solution 1:[1]

If you've already added a resize button that sticks to the corner of the div to be resized:

  1. Listen for a mousedown event on the button
  2. In the listener,
    • store the starting size and click position
    • add a mousemove listener onMouseMove to document.body that tracks the cursor position
    • add a mouseup listener that removes mouseMove when the drag is released
  3. Use the changes in cursor position to resize the div appropriately.

Example:

const { useState } = React;

function Resizeable({ children }) {
  const [size, setSize] = useState({ x: 400, y: 300 });

  const handler = (mouseDownEvent) => {
    const startSize = size;
    const startPosition = { x: mouseDownEvent.pageX, y: mouseDownEvent.pageY };
    
    function onMouseMove(mouseMoveEvent) {
      setSize(currentSize => ({ 
        x: startSize.x - startPosition.x + mouseMoveEvent.pageX, 
        y: startSize.y - startPosition.y + mouseMoveEvent.pageY 
      }));
    }
    function onMouseUp() {
      document.body.removeEventListener("mousemove", onMouseMove);
      // uncomment the following line if not using `{ once: true }`
      // document.body.removeEventListener("mouseup", onMouseUp);
    }
    
    document.body.addEventListener("mousemove", onMouseMove);
    document.body.addEventListener("mouseup", onMouseUp, { once: true });
  };

  return (
    <div id="container" style={{ width: size.x, height: size.y }}>
      <button id="draghandle" type="button" onMouseDown={handler} >Resize</button>
    </div>
  );
}

ReactDOM.render(<Resizeable />, document.getElementById("root"));
#root {
  height: 100vh;
  width: 100vw;
}

#container {
  position: relative;
  background-color: lightpink;
  border: solid red 1px;
}

#draghandle {
  position: absolute;
  bottom: 0;
  right: 0;
  transform: translate(50%, 50%);
}
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

Note that only the mousedown event is applied to the button, and the other handlers are applied to document.body. This makes sure that quickly moving the cursor off the button doesn't cause events to be missed.

Solution 2:[2]

So you'll need three pieces of information to do this. The location of the mouse when it first clicked the resize handle, the location of the mouse as it moves and the elements height and width.

Start with getting the elements height and width:

const [height, setHeight] = useState({ height: 20 }); // initialise to 20px
const [dragging, setDragging] = useState(false);
/** -- snip -- **/
<img style={{ height }} /* snip */ />

With an image, html will automatically handle the scaling for you, so you only need to apply the height property and the width will automatically be scaled.

Now we need to get the position of the mouse onClick of the resize handle. I assume you already have the styling for your handle so we can ignore that:

const [mouseStart, setMouseStart] = useState({ x: 0, y: 0 });
/** -- snip -- */
<ResizeHandle 
    onMouseDown={e => {
      setDragging(true);
      setMouseStart({ x: e.offsetX, y: e.offsetY });
    }}
/> 

Then you need to listen to the mouseMove event and resize the img appropriately - This should be done in the parent component:

  <div
     onMouseMove={e => {
        if (dragging) {
          const pixelDifference = Math.max(mouseStart.x - e.offsetX, mouseStart.y - e.offsetY);
          setHeight(height + pixelDifference);
        }
     }}

     onMouseUp={() => setDragging(false)}
  >
     <img /* snip */ />
     <ResizeHandle /* snip */ />
  </div>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Dylan Kerler