Circular Progress

Circular Progress ring that animates smoothly around a center button or icon

Last updated on

Edit on GitHub

Manual

Install the following dependencies:

npm install react-native-reanimated react-native-svg

Copy and paste the following code into your project. component/organisms/circular-progress

import React, { memo } from "react";import { Pressable, StyleSheet, View } from "react-native";import Animated, { useAnimatedProps } from "react-native-reanimated";import { Circle, Svg, type CircleProps } from "react-native-svg";import type { ICircularProgress } from "./types";const AnimatedCircle = Animated.createAnimatedComponent<CircleProps>(Circle);export const CircularProgress: React.FC<ICircularProgress> =  memo<ICircularProgress>((props: ICircularProgress): React.ReactNode => {    const {      progress,      size = 50,      strokeWidth = 3,      outerCircleColor = "rgba(255, 255, 255, 0.3)",      progressCircleColor = "white",      backgroundColor = "#502314",      gap: _gap = 2,      onPress,      renderIcon,    } = props;    const radius = (size - strokeWidth) / 2;    const circum = radius * 2 * Math.PI;    const circleAnimatedProps = useAnimatedProps<      Pick<CircleProps, "strokeDashoffset">    >(() => {      const progressValue = Math.min(Math.max(progress.value, 0), 100);      const strokeDashoffset = circum * (1 - progressValue / 100);      return {        strokeDashoffset,      };    });    const gap = _gap;    const innerCircleSize = size - strokeWidth * 2 - gap * 2;    const innerCirclePosition = strokeWidth + gap;    return (      <Pressable onPress={onPress}>        <View style={{ width: size, height: size }}>          <Svg width={size} height={size}>            <Circle              stroke={outerCircleColor}              fill="none"              cx={size / 2}              cy={size / 2}              r={radius}              strokeWidth={strokeWidth}            />            <AnimatedCircle              stroke={progressCircleColor}              fill="none"              cx={size / 2}              cy={size / 2}              r={radius}              strokeDasharray={`${circum} ${circum}`}              strokeLinecap="round"              transform={`rotate(-90, ${size / 2}, ${size / 2})`}              strokeWidth={strokeWidth}              animatedProps={circleAnimatedProps}            />          </Svg>          <View            style={[              styles.innerCircle,              {                width: innerCircleSize,                height: innerCircleSize,                backgroundColor,                top: innerCirclePosition,                left: innerCirclePosition,              },            ]}          >            {renderIcon ? (              renderIcon()            ) : (              <View                style={{                  width: innerCircleSize * 0.5,                  height: innerCircleSize * 0.5,                  backgroundColor: "#fff",                }}              />            )}          </View>        </View>      </Pressable>    );  });const styles = StyleSheet.create({  innerCircle: {    position: "absolute",    borderRadius: 100,    justifyContent: "center",    alignItems: "center",  },});

Usage

import { View, StyleSheet } from "react-native";import { FontAwesome, Ionicons } from "@expo/vector-icons";import { GestureHandlerRootView } from "react-native-gesture-handler";import { StatusBar } from "expo-status-bar";import { useEffect } from "react";import { useSharedValue, withTiming, Easing } from "react-native-reanimated";import { CircularProgress } from "@/components/organisms/circular-progress";export default function App() {  const progress = useSharedValue(0);  useEffect(() => {    progress.value = withTiming(72, {      easing: Easing.bezier(0.95, 0.1, 0.95, 1),      duration: 2000,    });  }, []);  return (    <GestureHandlerRootView style={styles.container}>      <StatusBar style="light" />      <View        style={{          marginTop: 100,        }}      >        <CircularProgress          progress={progress}          size={120}          strokeWidth={12}          outerCircleColor="rgba(255,255,255,0.15)"          progressCircleColor="#ff4757"          backgroundColor="#0000000f"          renderIcon={() => (            <FontAwesome name="heart" size={40} color="#ff4757" />          )}        />      </View>    </GestureHandlerRootView>  );}const styles = StyleSheet.create({  container: {    flex: 1,    backgroundColor: "#000",    alignItems: "center",  },});

Props

React Native Reanimated
React Native Svg