← Back toDrag and drop in React

Constrain a draggable element within its container

Written byPhuoc Nguyen
Created
06 Nov, 2023
Last updated
10 Nov, 2023
When designing draggable elements, it's often helpful to keep them within their parent container. This improves the user experience in several ways.
Firstly, constraining draggable elements within their parent container makes them easier to use. When elements are dragged outside of their container, it can be hard to interact with them, especially on smaller screens. By keeping them within their container, users can always access and interact with them easily.
Secondly, constraining draggable elements within their parent container increases visual clarity. When elements are allowed to be dragged outside their container, it can be difficult to understand their relationship to other elements on the page. By keeping them contained, users can clearly see their position and relationship to other elements.
Lastly, constraining draggable elements within their parent container can help prevent errors. When elements are dragged outside their container, they can be accidentally dropped in the wrong location or even lost altogether. By containing the elements within their parent container, you can reduce the risk of such errors occurring.
There are many scenarios where constraining draggable elements can be useful. For instance, when cropping an image, the selection box should be constrained within the bounds of the image itself. This ensures accuracy and precision when cropping to a specific size or aspect ratio.
Another example is a color picker, where constraining the draggable selector within its parent container ensures that users cannot accidentally drag it outside the boundaries of the color palette.
In this post, we'll learn how to develop this functionality with React. It's an important technique, so let's get started!

Constraining element movement within bounds

Let's take a step back and revisit the code where we made a specific element draggable. In our previous posts, we discussed an approach that utilized the `useRef()` hook to create a reference to the target element and attached it to the element using the `ref` attribute.
To ensure that our code works on both desktop and touchscreen devices, we handled the `mousedown` and `touchstart` events, respectively. We also utilized internal states that include `dx` and `dy` properties to keep track of the mouse's movement. These properties determine the horizontal and vertical distance the mouse has been moved.
To refresh your memory on what we've accomplished so far, here's a code snippet:
tsx
const eleRef = React.useRef();

const [{ dx, dy }, setOffset] = React.useState({
dx: 0,
dy: 0,
});

// Render
return (
<div
className="draggable"
ref={eleRef}
onMouseDown={handleMouseDown}
onTouchStart={handleTouchStart}
>
Drag me
</div>
);
To keep the draggable element within its parent container, we must calculate the maximum distance it can move in each direction. We can do this by subtracting the element's width and height from its parent container's width and height.
Here's how we handle the `mousemove` event when users move the target element:
ts
const handleMouseMove = (e: React.MouseEvent) => {
const ele = eleRef.current;
const parent = ele.parentElement;
const parentRect = parent.getBoundingClientRect();
const eleRect = ele.getBoundingClientRect();

// How far the mouse has been moved
let dx = e.clientX - startPos.x;
let dy = e.clientY - startPos.y;

// Calculate maximum distance
const maxX = parentRect.width - eleRect.width;
const maxY = parentRect.height - eleRect.height;

// Constrain movement within bounds
dx = Math.max(0, Math.min(dx, maxX));
dy = Math.max(0, Math.min(dy, maxY));

// Set the position of element
ele.style.transform = `translate(${dx}px, ${dy}px)`;

// Reassign the position of mouse
setOffset({ dx, dy });
};
The `handleMouseMove()` function is in charge of responding to the `mousemove` event. First, it gets the bounding rectangle of the element's parent container and its own bounding rectangle using `getBoundingClientRect()`. Then, it calculates how far the mouse has moved since it was last clicked.
After that, the function computes the maximum distance that the element can be moved in each direction by subtracting its width and height from those of its parent container. This is important because we don't want our draggable element to move outside of its parent container's boundaries.
Finally, to keep the draggable element within bounds, we use `Math.max()` and `Math.min()` methods to make sure it doesn't exceed its parent container's boundaries. The resulting `dx` and `dy` values are used to set the position of our draggable element using CSS transforms.
If you want, you can create a helper function that clamps a given number within a range, ensuring that the returned value is within the range.
ts
const clamp = (val: number, min: number, max: number): number => Math.max(min, Math.min(max, val));
We can set the values of `dx` and `dy` like this:
ts
dx = clamp(dx, 0, maxX);
dy = clamp(dy, 0, maxY);
We have added some lines of code to our current implementation, which means our draggable element is now restricted within its parent container.
Check out the demo below and give it a try yourself. Try to drag the target element to the edge of its container, and you'll notice that it's not possible to move it outside the container.

Creating a reusable hook

In our previous discussion, we covered how to create a custom hook to encapsulate the draggable element implementation. This approach saves time and helps us write cleaner and more organized code. Custom hooks also allow us to take complex functionality and turn it into simple hooks that can be shared with other developers or reused in future projects.
Adding constraint functionality to the hook we've created is an easy task. You can find the full implementation in the `useDraggable.ts` code.

Conclusion

When it comes to creating draggable elements in React, keeping them contained within their parent container can greatly improve the user experience and prevent errors. By doing so, users can easily interact with the elements, understand their relationship to other elements on the page, and avoid accidentally dragging them outside of the container.
In this post, we've covered how to create draggable elements in React and add constraint functionality to keep them within their container. We've even explored creating a reusable hook to make it all easier.
By following these techniques, you'll be able to create more intuitive and polished UIs in your React projects. Always keep your users in mind when designing draggable elements and strive for simplicity and clarity in your code. With these tips under your belt, you're ready to build great UIs that users will love.

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