Pulsing Loader

Multiple dots that smoothly pulse in a sequence

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/pulsing-dots

// PulsingDots.tsximport React, { useEffect } from "react";import { View, StyleSheet } from "react-native";import Svg, { Circle, Defs, LinearGradient, Stop } from "react-native-svg";import Animated, {  useSharedValue,  useAnimatedProps,  withRepeat,  withTiming,  Easing,} from "react-native-reanimated";import type { PulsingDotsProps } from "./PlusingDots.types";const AnimatedCircle = Animated.createAnimatedComponent(Circle);export const PulsingDots: React.FC<PulsingDotsProps> = ({  dotCount = 3,  radius = 6,  spacing = 25,  duration = 800,  color = "#00C896",  gradient,}): React.ReactNode & React.JSX.Element => {  const opacities = Array.from({ length: dotCount }, () => useSharedValue(0.3));  useEffect(() => {    opacities.forEach((opacity, i) => {      setTimeout(() => {        opacity.value = withRepeat(          withTiming(1, {            duration,            easing: Easing.inOut(Easing.ease),          }),          -1,          true,        );      }, i * 200);    });  }, []);  const animatedProps = opacities.map((val) =>    useAnimatedProps(() => ({ opacity: val.value })),  );  const totalWidth = radius * 2 + (dotCount - 1) * spacing;  return (    <View style={styles.container}>      <Svg width={totalWidth} height={radius * 3}>        <Defs>          {gradient?.map((g, i) => (            <LinearGradient              key={i}              id={`grad-${i}`}              x1="0"              y1="0"              x2="1"              y2="1"            >              <Stop offset="0%" stopColor={g.from} />              <Stop offset="100%" stopColor={g.to} />            </LinearGradient>          ))}        </Defs>        {animatedProps.map((props, i) => {          const gradientId = gradient?.[i]            ? `url(#grad-${i})`            : gradient?.[0]              ? `url(#grad-0)`              : color;          return (            <AnimatedCircle              key={i}              cx={radius + i * spacing}              cy={radius * 1.5}              r={radius}              fill={gradientId}              animatedProps={props}            />          );        })}      </Svg>    </View>  );};const styles = StyleSheet.create({  container: {    height: 40,    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,  PulsingDots,} 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}>        <PulsingDots color="#fff" />      </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