← Back toDrag and drop in React

Create a radial progress bar

Written byPhuoc Nguyen
Created
18 Nov, 2023
Tags
React radial progress bar
The radial progress bar is a graphic representation of progress made towards a specific goal or milestone. It's commonly used in web and mobile apps to show the completion percentage of tasks like uploading a file or downloading data. The goal of the radial progress bar is to help users track their progress in an engaging and intuitive way, keeping them motivated to finish the task at hand. With a circular shape and color-coded segments, the radial progress bar makes it easy for users to see how much work is left and how far they've come.
There are countless applications for the radial progress bar. For instance, fitness apps can use it to show users how close they are to reaching their fitness goals, while e-commerce sites can use it to encourage users to complete their purchases. Online courses can use it to help students stay on track, and project management tools can use it to show team members how much progress has been made towards completing a project.
In this post, we'll explore how to create a radial progress bar with React. By providing users with clear feedback and keeping them engaged, the radial progress bar can improve motivation and drive better results in a variety of contexts.

Dragging an element along a circle

In our previous post, we explored how to drag an element along a circle. And to make this functionality reusable, we created a custom hook. Using this hook is incredibly simple: all we need to do is call a function.
ts
const [draggableRef, dx, dy] = useDraggable();
The function we're using returns an array with three items. The first item is the reference of the target element, which we can attach using the `ref` attribute. The last two items show how far the element has been moved, specifically the distance between the top-left corner of the element and its container. We can use these values to position the element by passing them to the `translate` function.
To remind you what we've done earlier, here's the code snippet.
tsx
<div className="container">
<div
className="draggable"
ref={draggableRef}
style={{
transform: `translate(${dx}px, ${dy}px)`,
}}
/>
</div>
Please check out the demo below:
As you can see, there is an issue where the element isn't positioned properly at the beginning. Additionally, once users drag the element into the boundary of the circle, there's no way to know how many percentages it has been moved. This is a crucial requirement for a radial progress bar.
To address both of these issues, the hook should have an additional parameter, such as `initialAngle`, which indicates the initial angle of the progress bar. The updated version of the hook would then return a new angle value indicating the current angle based on the current position.
Here's an example of what the updated version of the hook could look like:
ts
const useDraggable = ({ initialAngle }) => {
return [ref, dx, dy, angle];
};
We can calculate the angle of our draggable element by using basic trigonometry. To do this, we simply measure the angle between the center of the container and the current position of the element.
ts
const radians = Math.atan2(dy - center, dx - center);
const angle = (radians + Math.PI) / (Math.PI * 2);
setAngle(angle);
We can use the `useEffect` hook to calculate the initial position of a draggable element based on the provided `initialAngle`. First, we get 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. After that, we determine the center point of the circle by finding half of the difference between the container width and the draggable element width.
Using trigonometry, we calculate `dx` and `dy` values based on the initial angle, using `Math.cos()` and `Math.sin()` functions.
Finally, we update our state with the new values for `dx` and `dy`, so our draggable element starts at a specific position on our circular path.
Here's an example of how you can implement this:
ts
React.useEffect(() => {
const width = node.getBoundingClientRect().width;
const containerWidth = node.parentElement.getBoundingClientRect().width;
const radius = containerWidth / 2;
const center = radius - width / 2;

const radian = initialAngle * Math.PI * 2 - Math.PI;
const dx = center + radius * Math.cos(radian);
const dy = center + radius * Math.sin(radian);

setOffset({ dx, dy });
}, [node]);
By utilizing the `useEffect` hook in this manner, we can establish an initial position for our draggable element based on any preferred angle along a circular path.

Displaying percentage values

To set up the radial progress bar, we need to move the drag handler inside the container. To do this, we wrap it in a circle and use absolute positioning. We set the `top`, `left`, `right`, and `bottom` properties to make sure the circle completely fills its parent container.
css
.radial-progress-bar__circle {
position: absolute;
top: 0.5rem;
left: 0.5rem;
right: 0.5rem;
bottom: 0.5rem;
}
We can style the circle using CSS to make it look the way we want, such as by setting a background color or adding a border. Putting the drag handler inside the circle makes it easier for users to interact with our radial progress bar.
We've also made an update to show the percentage values based on where the drag handler is positioned. The current angle is now included in the array returned by the hook.
ts
const [draggbleRef, dx, dy, angle] = useDraggable({
initialAngle: 1,
});
The `angle` variable returns a decimal value between 0 and 1. To determine the percentage of progress based on the position of the draggable element, we multiply the decimal value by 100. This gives us a whole number between 0 and 100. Finally, we use `Math.round()` to round the decimal value of `angle` to the nearest whole number.
ts
<div className="radial-progress-bar__circle">
{Math.round(angle * 100)}%
</div>
In this example, we show the percentage value as a string inside the circle. This makes it easy for users to see how far they've come in completing their task or goal represented by the radial progress bar.
To center the percentage value inside its container, we can use CSS flexbox. By applying `align-items: center` and `justify-content: center` to the circle element, we ensure that the percentage value is centered both horizontally and vertically.
Here's an example of how this can be implemented:
css
.radial-progress-bar__circle {
align-items: center;
display: flex;
justify-content: center;
}
With these CSS properties, we can easily center the percentage value within its container. This gives our radial progress bar a clean and polished appearance.
Take a look at the demo below. Simply drag the small circle and the corresponding percentage will be displayed at the center of the container.

Adding a curve

Our radial progress bar currently shows the current percentage value based on the angle of the drag handler, but it doesn't indicate the completed portion with a curve. To solve this problem, we'll overlap an inner circle on top of a pie chart to create a curve.
We won't go into the details of creating a pie chart, but if you're interested, check out this helpful post. In that post, the pie chart is generated by two halves of the container.
In this example, we rotate the second half using the `rotate` function and dynamically pass the current angle to it. It's important to note that the angle is a number between 0 and 1, so we multiply it by 360 since 360 degrees represent a full circle.
tsx
<div className="radial-progress-bar__half radial-progress-bar__half--1" />
<div
className="radial-progress-bar__half radial-progress-bar__half--2"
style={{
background: angle > 0.5 ? 'rgb(99 102 241)' : 'inherit',
transform: `rotate(${angle > 0.5 ? 360 * angle - 180 : 360 * angle}deg)`,
}}
/>
Next, we'll add an overlay to our pie chart and inner circle to create a sleek curve effect. The overlay is circular, achieved by setting its `border-radius` property to 50%. We've set the `background` property to white so that it blends seamlessly with the container's background color.
To position the overlay perfectly, we've used CSS properties like `top`, `left`, `right`, and `bottom`, all set to 1rem. This ensures that the overlay fully covers both halves of the pie chart.
With these simple CSS tricks, we can create a visually appealing radial progress bar that shows progress towards completion.
css
.radial-progress-bar__overlay {
border-radius: 50%;
background: #fff;

position: absolute;
top: 1rem;
left: 1rem;
right: 1rem;
bottom: 1rem;
}
Check out the final demo below:

Conclusion

To sum up, creating a radial progress bar with React isn't as tough as it may seem. We can easily calculate the position of a draggable element along a circular path by using a custom hook. Also, applying CSS styles to our elements can help us create an attractive and practical radial progress bar that allows users to track their progress towards completion.
This type of UI element is especially helpful in situations where users need to track their progress towards completing a task or goal. With its simple design and easy-to-use functionality, a radial progress bar can enhance user engagement and satisfaction.
However, it's essential to consider the specific needs of your application or website when implementing a radial progress bar. By taking into account factors such as color scheme, placement within the overall design, and ease of use for your target audience, you can create a truly effective and engaging radial progress bar that helps users achieve their goals.

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