Orbitdot Loader

A pulsating center circle with multiple dots

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/molecules/orbiting-dots

// SpinnerSegments.tsximport React, { useEffect } from "react";import { StyleSheet, View, ViewStyle } from "react-native";import Svg, { Circle } from "react-native-svg";import Animated, {  useSharedValue,  useAnimatedStyle,  useAnimatedProps,  withTiming,  withRepeat,  withSequence,  Easing,} from "react-native-reanimated";const AnimatedView = Animated.createAnimatedComponent(View);const AnimatedCircle = Animated.createAnimatedComponent(Circle);export interface OrbitDotLoaderProps {  dotColor?: string;  dotRadius?: number;  centerRadius?: number;  size?: number;  duration?: number;  numDots?: number;  style?: ViewStyle;}export const OrbitDotLoader: React.FC<OrbitDotLoaderProps> = ({  dotColor = "#fff",  dotRadius = 4,  centerRadius = 5,  size = 40,  duration = 900,  numDots = 4,  style,}) => {  const rotation = useSharedValue(0);  const centerScale = useSharedValue(1);  useEffect(() => {    rotation.value = withRepeat(      withTiming(360, {        duration,        easing: Easing.linear,      }),      -1,    );  }, [duration]);  useEffect(() => {    centerScale.value = withRepeat(      withSequence(        withTiming(1.3, { duration: 400, easing: Easing.out(Easing.ease) }),        withTiming(1, { duration: 400, easing: Easing.in(Easing.ease) }),      ),      -1,    );  }, []);  const rotateStyle = useAnimatedStyle(() => ({    transform: [{ rotate: `${rotation.value}deg` }],  }));  const centerProps = useAnimatedProps(() => ({    r: centerRadius * centerScale.value,  }));  const center = size / 2;  const orbitY = center - size * 0.3;  return (    <View style={[styles.container, { width: size, height: size }, style]}>      <Svg width={size} height={size}>        {/* Pulsing center circle */}        <AnimatedCircle          cx={center}          cy={center}          r={centerRadius}          fill={dotColor}          animatedProps={centerProps}        />      </Svg>      {/* Rotating orbiting dots */}      <AnimatedView style={[styles.spinner, rotateStyle]}>        <Svg width={size} height={size}>          {Array.from({ length: numDots }).map((_, i) => {            const angle = (360 / numDots) * i;            return (              <Circle                key={i}                cx={center}                cy={orbitY}                r={dotRadius}                fill={dotColor}                transform={`rotate(${angle}, ${center}, ${center})`}              />            );          })}        </Svg>      </AnimatedView>    </View>  );};const styles = StyleSheet.create({  container: {    justifyContent: "center",    alignItems: "center",  },  spinner: {    position: "absolute",    justifyContent: "center",    alignItems: "center",  },});

Usage

import { View, Text, StyleSheet } from "react-native";import { GestureHandlerRootView } from "react-native-gesture-handler";import { StatusBar } from "expo-status-bar";import { useFonts } from "expo-font";import { SymbolView } from "expo-symbols";import { DisclosureGroup } from "@/components/molecules/disclosure-group";import DynamicText from "@/components/molecules/dynamic-text";import { DynamicTextItem } from "@/components/molecules/dynamic-text/types";import GooeyText from "@/components/molecules/gooey-text";import { CircleLoadingIndicator, OrbitDotLoader } from "@/components";import { CircularLoader } from "@/components/molecules/Loaders/circular";export default function App() {  const [fontLoaded] = useFonts({    SfProRounded: require("@/assets/fonts/sf-pro-rounded.ttf"),    HelveticaNowDisplay: require("@/assets/fonts/HelveticaNowDisplayMedium.ttf"),  });  const OPTIONS = [    { label: "Edit", icon: "pencil" },    { label: "Duplicate", icon: "doc.on.doc" },    { label: "Share", icon: "square.and.arrow.up" },    { label: "Delete", icon: "trash", destructive: true },  ];  const GOOEY_TEXTS: string[] = ["REACTICX", "IS", "AWESOME!"];  return (    <GestureHandlerRootView style={styles.container}>      <StatusBar style="light" />      <View style={styles.content}>        <OrbitDotLoader numDots={3} size={40} />      </View>    </GestureHandlerRootView>  );}const styles = StyleSheet.create({  container: {    flex: 1,    backgroundColor: "#0a0a0a",  },  content: {    paddingHorizontal: 20,    paddingTop: 90,    justifyContent: "center",    alignItems: "center",    gap: 0,  },  title: {    fontSize: 28,    fontWeight: "700",    color: "#fff",  },  subtitle: {    fontSize: 15,    color: "#555",  },  card: {    backgroundColor: "#141414",    borderRadius: 16,    overflow: "hidden",    marginTop: 20,  },  triggerContent: {    padding: 16,  },  triggerLeft: {    flexDirection: "row",    alignItems: "center",    gap: 12,  },  triggerText: {    fontSize: 16,    fontWeight: "500",    color: "#fff",  },  item: {    flexDirection: "row",    alignItems: "center",    gap: 12,    padding: 14,    backgroundColor: "#1a1a1a",    borderRadius: 12,    marginBottom: 6,  },  itemText: {    fontSize: 15,    color: "#fff",  },  destructiveText: {    color: "#ff453a",  },});

Props

React Native Reanimated
React Native Svg