Lightweight Progress Circles in React Native

React Native Progress Circles

I decided to create my own React Native component to display progress circles. It's called react-native-progress-circle and you can easily use it in your React Native app. I didn't use an existing component, because I wanted it to be small and lightweight. It does not depend on react-native-svg or ART, it's done purely in CSS.

#How it works

It uses two circles and two half-circles and positions them in a smart way such that they look like the progress circles you see above. (Inspired by react-native-percentage-circle.) It's done in four steps:

#1. Render an outer background circle with the shadow border color.

React Native Progress Circle background

It's just a styled View:

<View
  style={[styles.outerCircle, {
    width: radius * 2,
    height: radius * 2,
    borderRadius: radius,
    backgroundColor: shadowColor,
  }]}
/>

#2. Render a half-circle and position it according to the percentage

Now we style a View to the size of a rectangle and set the borderRadius property of only two of the four corners. This creates a half-circle.

React Native Progress Circle background

<View
  style={[styles.outerCircle, {
    width: radius,
    height: radius * 2,
    backgroundColor: color,
    borderRadius: radius,
    borderTopRightRadius: 0,
    borderBottomRightRadius: 0,
    transform: [ { rotate: /* rotate it according to percentage */ } ],
  }]}
/>

#3. Render another half-circle and position it according to the percentage

If the percentage is greater than 50%, we need another half-circle and then rotate it. It will overlay the first half-circle to some degree.

React Native Progress Circle background

#4. Render a smaller inner circle with the original background color

Now just put a smaller white circle in the center:

React Native Progress Circle background

const radiusMinusBorder = this.props.radius - this.props.borderWidth
// ...
<View
  style={[styles.innerCircle, {
    width: radiusMinusBorder * 2,
    height: radiusMinusBorder * 2,
    borderRadius: radiusMinusBorder,
    backgroundColor: this.props.bgColor,
  }]}
>
  {
      this.props.children
  }
</View>

#Rotation Issues

Two half-circles are enough to cover all percentages from 0 to 100, however you need to be a bit smart about it:

#1. More than 50%:

This is the easy case. The first half-circle will always have a rotation value of 180deg, spanning the right side of the circle. The second one is computed easily by converting the percent to degrees:

function percentToDegrees(percent) {
  return percent * 3.6
}

#2. Less than 50%:

We do the same rotation conversion here, but the problem is that we display the full half-circle instead of only the part on the right circle side.

React Native Progress Circle background

The solution is use the second half-circle as an overlay for the left side. We render it there (0deg) in the color of the outer circle.

React Native Progress Circle less than 50 percent

#Further Usage with Animated Library

An advantage of only using the built-in CSS features is that we can use the Animated library that provides smooth animations with hardware acceleration. We can then create a simple Countdown Circle with the same concepts.

React Native Countdown Indicator