← Back toDrag and drop in React

Make a given element resizable

Written byPhuoc Nguyen
Created
05 Nov, 2023
Last updated
10 Nov, 2023
Tags
React resizable element
Resizable elements are a must-have feature in web design that can greatly improve the user experience. By allowing users to adjust the size of elements on a webpage, they can interact with content more conveniently.
Resizable columns, for example, can make it easier for users to view and compare information, especially when dealing with large amounts of data. By adjusting the width of each column, users can focus on the most important data without being overwhelmed by unnecessary details.
Image editors like Adobe Photoshop or GIMP often incorporate the ability to resize images, which is extremely helpful for designers and photographers who need to adjust image size and resolution for various purposes. By resizing an image, users can ensure that it fits within specific dimensions or file size restrictions without sacrificing quality.
In summary, resizable elements are a crucial aspect of web design that can significantly enhance the user experience. In this post, we will learn how to make an element resizable using React.

Preparing the layout

To enable our element to be resizable, we'll start by arranging the markup. Typically, we resize an element by dragging its corners or sides. To simplify things, we'll only let users drag the right and bottom sides of the element.
Here's a visual representation of the element's layout:
tsx
<div className="resizable">
<div className="resizer resizer--r" />
<div className="resizer resizer--b" />
</div>
To create a resizable container with handles that allow the user to adjust its size, we use the `.resizable` class. This class has a `position: relative` property, which allows the resizer elements to be positioned absolutely within it. Additionally, we use the `.resizer` class to create the handles themselves. This class has a `position: absolute` property, which positions the handles inside the resizable container.
css
.resizable {
position: relative;
}
.resizer {
position: absolute;
}
In our particular use case, we have limited the resizing capability to only the right and bottom sides. Here's an example of what the styles for the right resizer could look like:
css
.resizer--r {
cursor: col-resize;

right: 0;
top: 50%;
transform: translate(50%, -50%);

height: 2rem;
width: 0.25rem;
}
The `resizer--r` class is responsible for resizing an element from its right side. It changes the mouse pointer to a horizontal line with arrows pointing left and right, allowing the user to resize the element horizontally. This class sets the `right` property to 0, positioning it at the far right of the resizable element. Additionally, the `top` property is set to 50%, and `transform: translate(50%, -50%)` is used to center it vertically. This ensures that it always appears in the middle of the right edge, no matter how tall the resizable element is. Finally, the resizer's width is set to `0.25rem`, so it doesn't take up too much space, and its height is set to `2rem`, providing enough surface area for users to click and drag.
Similarly, we can use the following CSS class to position another resizer indicator at the center of the bottom side:
css
.resizer--b {
cursor: row-resize;

bottom: 0;
left: 50%;
transform: translate(-50%, 50%);

height: 0.25rem;
width: 2rem;
}
You can easily customize these classes by tweaking the CSS properties to support other resizer placements.
To improve the user experience, we can add a cool hover effect to the resizable element. When a user hovers over the resizable element, we can make the resizer handles stand out by changing their background color. This effect can be achieved using CSS by setting the background of the `.resizer` class to `transparent`. Then, using the `:hover` pseudo-class on the `.resizable` container, we can change the background color of all `.resizer` elements within it to a nice shade of blue that pops against most backgrounds.
css
.resizer {
background: transparent;
}
.resizable:hover .resizer {
background: rgb(99 102 241);
}
With these changes, users will have a clear understanding of which parts of the resizable element they can interact with and resize. To give you an idea of the new layout, here's a preview without the actual resize functionality.

Dragging the handlers

When it comes to resizing an element, users can easily drag the resizers located on the right or bottom sides. Similarly, we can use this technique to create a draggable element. For our purposes, we only support two dragging directions: horizontal, to change the width of the element, and vertical, to change its height.
To define the possible directions, we can utilize a Typescript enum.
ts
enum Direction {
Horizontal = 'Horizontal',
Vertical = 'Vertical',
}
To make the target element draggable on both desktop and touchscreen devices, we need to handle the `mousedown` and `touchstart` events, just like we did before. In case you need a refresher, here's a quick code snippet to remind you of what we did:
tsx
<div
className="resizer resizer--r"
onMouseDown={handleMouseDown}
onTouchStart={handleTouchStart}
/>
<div
className="resizer resizer--b"
onMouseDown={handleMouseDown}
onTouchStart={handleTouchStart}
/>
In the past, we positioned draggable elements by setting their position within their container and updating their position with the `transform` property. But for our resizable functionality, we've switched things up. We no longer use internal states like `dx` and `dy`. Instead, we store the starting point and current dimensions of the element when users start dragging the resizer.
Here's how we handle the `mousedown` event:
ts
const handleMouseDown = (e) => {
const ele = elementRef.current;
const direction = e.target.classList.contains("resizer--r")
? Direction.Horizontal
: Direction.Vertical;
const startPos = {
x: e.clientX,
y: e.clientY,
};

const styles = window.getComputedStyle(ele);
const w = parseInt(styles.width, 10);
const h = parseInt(styles.height, 10);
// ...
};
When a user clicks on a resizer element, we use the `handleMouseDown` function to capture the mouse coordinates with `e.clientX` and `e.clientY`. Then, we calculate the current width and height of the resizable element using `window.getComputedStyle(ele)`, where `ele` is the target HTML element.
To determine the resizing direction, we check which resizer element was clicked. If the right resizer element was clicked (with the class `resizer--r`), we set the direction to `Direction.Horizontal` to allow horizontal resizing. If the bottom resizer element was clicked, we set it to `Direction.Vertical` to allow vertical resizing. We use this information later to calculate the new dimensions of the resizable element based on how far the user drags the resizer handle.
As the user moves the mouse, we calculate the horizontal and vertical distance the mouse moves. Rather than updating internal states like before, we can simply update the dimension of the target element.
ts
const handleMouseMove = (e, direction) => {
const dx = e.clientX - startPos.x;
const dy = e.clientY - startPos.y;

direction === Direction.Horizontal
? ele.style.width = `${w + dx}`
: ele.style.height = `${h + dy}`;
updateCursor(direction);
};
The `handleMouseMove` function updates the size of a resizable element as the user moves the mouse. It calculates how far the mouse has moved since the user clicked on the element. Then, based on the direction specified by `direction`, it updates either the width or height of the element using `ele.style.width` or `ele.style.height`. The initial width and height of the element are stored in `w` and `h`, respectively.
After updating the size of the element, the `updateCursor()` function is called to update the cursor style as the user drags a resizer handle. This gives users feedback on whether they're resizing horizontally or vertically.
ts
const updateCursor = (direction) => {
document.body.style.cursor = direction === Direction.Horizontal
? 'col-resize'
: 'row-resize';
document.body.style.userSelect = 'none';
};
The function takes a `direction` parameter, which tells it which way the user is dragging. If it's horizontal, the cursor changes to `col-resize`, which looks like a horizontal line with arrows pointing left and right. If it's vertical, the cursor changes to `row-resize`, which looks like a vertical line with arrows pointing up and down.
We also use CSS to set `user-select` to none, so users can't accidentally highlight text while dragging the resizer. This makes for a smoother user experience.
Just remember to reset the cursor when the user releases the mouse.
ts
const resetCursor = () => {
document.body.style.removeProperty('cursor');
document.body.style.removeProperty('user-select');
};
Try out this demo! Hover over the left or bottom side of the element to see the resizer handlers, and then drag them horizontally or vertically to adjust the element's size.

Creating a custom hook for resizable functionality

Making an element resizable is essential in many real-life situations, as we mentioned earlier. So, let's package it up and reuse it across multiple components.
In React, we can achieve this by creating custom hooks. Custom hooks not only save time but also help us write cleaner, more organized code. Plus, they allow us to take complex functionality and turn it into simple hooks that can be shared with other developers or reused in future projects.
Creating a custom hook for resizable functionality is similar to creating one for making an element draggable, which we covered previously. If you want to dive deep into the details of creating the custom hook, you can find it in our previous post.
There is only one notable difference between the custom hook and the implementation we covered in the previous section. In the previous implementation, we declared the event handlers to the resizers directly.
tsx
<div
className="resizer resizer--r"
onMouseDown={handleMouseDown}
onTouchStart={handleTouchStart}
/>
<div
className="resizer resizer--b"
onMouseDown={handleMouseDown}
onTouchStart={handleTouchStart}
/>
However, in order to use a custom hook for resizing, we must manually determine the resizer elements and attach the event to them.
To allow for multiple resizers in a resizable container, we can query the resizer elements and attach event handlers to each of them. This can be done using `node.querySelectorAll(".resizer")` inside the effect hook that is called when the ref is set. This will give us an array-like object containing all elements with a class of `.resizer`. We can then iterate over this array and attach `mousedown` and `touchstart` event listeners to each element. Finally, the event listeners are removed when the user releases the mouse button.
Here's some sample code to help you understand the process.
ts
React.useEffect(() => {
if (!node) {
return;
}
const resizerElements = [...node.querySelectorAll(".resizer")];
resizerElements.forEach((resizerEle) => {
resizerEle.addEventListener("mousedown", handleMouseDown);
resizerEle.addEventListener("touchstart", handleTouchStart);
});

return () => {
resizerElements.forEach((resizerEle) => {
resizerEle.removeEventListener("mousedown", handleMouseDown);
resizerEle.removeEventListener("touchstart", handleTouchStart);
});
};
}, [node]);
Check out the demo below to see how we encapsulate the resizable implementation in a reusable hook.

Conclusion

To sum up, using React to create resizable elements is an excellent way to enhance your web application's user experience. By utilizing CSS and event handling in React, we can develop resizable elements that function smoothly across all devices and provide users with visual feedback as they resize elements.
Furthermore, we have learned how to create a custom hook for resizable functionality, which simplifies the implementation details and makes it convenient to reuse the same code across multiple components. This not only saves time and effort but also makes our code more organized and easier to maintain.
In essence, making an element resizable is a fantastic approach to improving the usability of your web application. With the techniques discussed in this post, you'll be able to make any element resizable in no time!

See also

If you found this post helpful, please consider giving the repository a star on GitHub or sharing the post on your favorite social networks 😍. Your support would mean a lot to me!

Questions? 🙋

Do you have any questions about front-end development? If so, feel free to create a new issue on GitHub using the button below. I'm happy to help with any topic you'd like to learn more about, even beyond what's covered in this post.
While I have a long list of upcoming topics, I'm always eager to prioritize your questions and ideas for future content. Let's learn and grow together! Sharing knowledge is the best way to elevate ourselves 🥷.
Ask me questions

Recent posts ⚡

Newsletter 🔔

If you're into front-end technologies and you want to see more of the content I'm creating, then you might want to consider subscribing to my newsletter.
By subscribing, you'll be the first to know about new articles, products, and exclusive promotions.
Don't worry, I won't spam you. And if you ever change your mind, you can unsubscribe at any time.
Phước Nguyễn