'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:
- Listen for a
mousedown
event on the button - In the listener,
- store the starting size and click position
- add a
mousemove
listeneronMouseMove
todocument.body
that tracks the cursor position - add a
mouseup
listener that removesmouseMove
when the drag is released
- 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 |