← Back toDrag and drop in React

Drag an element along a circle

Written byPhuoc Nguyen
Created
16 Nov, 2023
From the beginning of this series, we learned how to make an element draggable and covered many real-world examples that take advantage of this functionality. In these examples, the target element can be moved both horizontally and vertically. When building a horizontal slider, we restrict the movement of the knob to a horizontal line only.
However, in reality, there are many situations where an element's trajectory can take on various shapes, such as a circle.
Dragging elements along circular paths is a useful technique in various user interfaces and data visualizations. For example, circular sliders or knobs are often used in music production software to adjust different parameters like volume or filter cutoff frequency. Circular charts or gauges are used in data visualizations to represent values that range from 0 to 360 degrees, such as the distribution of crimes in a city over a year. Some virtual reality applications use circular paths for navigation, and games use circular controls for steering vehicles or aiming weapons.
This concept isn't just limited to web development but also has practical applications in the real world. For instance, think about a volume knob on an audio system that can be dragged around a circle to adjust the sound level. Similarly, consider a circular dial on a thermostat that can be turned to set the desired temperature. In video games, players often use joysticks to move characters or objects around in circles.
Overall, dragging elements along a circle can improve user experience by making interfaces more intuitive and user-friendly.
In this post, we'll learn how to drag an element along a circle with React.

Making an element draggable

Let's review how we made an element draggable. To achieve this functionality, we created a reusable hook called `useDraggable`.
Using the hook is simple. Just call the function and it will return an array of three items.
ts
const [draggableRef, dx, dy] = useDraggable();
To drag an element around, we need to know three things: the target element we want to drag, and the distance it has been dragged horizontally (`dx`) and vertically (`dy`) from its original position. We can attach the target element using the `ref` attribute, and position it within its container using the `translate` function and the `transform` property.
Here's a sample code to remind you of what we did:
tsx
<div className="container">
<div
className="draggable"
ref={draggableRef}
style={{
transform: `translate(${dx}px, ${dy}px)`,
}}
/>
</div>
In this example, both the target element and the container have a circular shape. To achieve this, we set the same value for both the `height` and `width` properties and then adjust the `border-radius` to 50%. This will generate a perfect circular shape.
css
.container {
border-radius: 50%;
height: 12rem;
width: 12rem;
}
.draggable {
border-radius: 50%;
height: 1rem;
width: 1rem;
}
Try out this demo! You can move the smaller circle around by dragging it. Right now, there are no restrictions on movement, so feel free to place it wherever you like.

Moving an element along a circle

To move an element along a circle, we've kept things simple. When the user clicks on the draggable element, we capture the starting position of the mouse cursor.
The following function handles the `mousedown` event. It takes an event as its parameter and extracts the current coordinates of the mouse pointer using `e.clientX` and `e.clientY`. We then calculate the horizontal (`dx`) and vertical (`dy`) distances from their original positions to determine the correct starting point of our element. It's the same process we use when making an element draggable.
ts
const handleMouseDown = ((e) => {
const startPos = {
x: e.clientX - dx,
y: e.clientY - dy,
};
};
By storing the initial position, we can determine how much to move our element as the user drags it.
As the user drags the element by moving their mouse, the `handleMouseMove` function is called. This function takes an event as its parameter and calculates the new horizontal (`dx`) and vertical (`dy`) distances from the initial position of the mouse cursor to its current position.
ts
const handleMouseMove = (e) => {
let dx = e.clientX - startPos.x;
let dy = e.clientY - startPos.y;
setOffset({ dx, dy });
};
The `setOffset` function updates the state with new values for `dx` and `dy` based on the position of the mouse. This way, the element moves along with the mouse.
But, to make sure the element stays within the circle, we need to tweak the calculation a bit. We use some basic trigonometry to get this done.
First, we find the width of both the draggable element and its container using `getBoundingClientRect()`. Then, we calculate the radius of the circle by dividing the container width by 2. Finally, we calculate the center point of the circle by finding half the difference between the container width and draggable element width.
ts
const width = node.getBoundingClientRect().width;
const containerWidth = node.parentElement.getBoundingClientRect().width;
const radius = containerWidth / 2;
const center = radius - width / 2;
If you take a look at the source code of the `useDraggable` hook below, you'll notice that we store certain values inside the `mousedown` event instead of the `mousemove` event handlers. This ensures that these values won't be recalculated during movement, leading to better performance. It's a small trick, but it can make a big difference.
Next, we calculate the distance from the center point to our current mouse position using Pythagoras' theorem. This allows us to calculate both sine and cosine values based on the distance.
ts
const centerDistance = Math.sqrt(
Math.pow(dx - center, 2) + Math.pow(dy - center, 2)
);
const sinValue = (dy - center) / centerDistance;
const cosValue = (dx - center) / centerDistance;
Finally, we update the horizontal (`dx`) and vertical (`dy`) distances with new values that are calculated using sine and cosine values. This ensures that our draggable element moves along a circular path within its container.
ts
dx = center + radius * cosValue;
dy = center + radius * sinValue;
Take a look at this demo. Don't worry about where the target element starts out, which is at the top-left corner of its container. Once you click and move it around, it will automatically lock onto the path of the larger circle.

Conclusion

In this post, we've learned how to drag an element along a circular path using React. We started by reviewing how to make an element draggable and then moved on to adjusting its movement along a circle. By using basic trigonometry and mouse event listeners, we were able to move our target element along a circular path within its container.
Dragging elements along a circle has many practical applications, such as virtual reality navigation, game controls, music production software, and data visualizations. It's an excellent way to enhance user experience and make interfaces more intuitive.
With React hooks like `useDraggable`, we can easily add interactivity and dynamic functionality to our web applications. By combining some math knowledge with a little creativity, we can build sophisticated user interfaces that are both aesthetically pleasing and user-friendly.
Stay tuned for our upcoming posts where we'll explore some real-life examples of this functionality in action.
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