Circular Progress
Circular Progress ring that animates smoothly around a center button or icon
Last updated on
Manual
Install the following dependencies:
npm install react-native-reanimated react-native-svgCopy 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
