Components
Advanced Loading Spinner
Advanced Loading Spinner
Multi-phase animated spinner with dynamic color transitions and orbital effects for enhanced user feedback.
Installation
1
Install the packages
npm i motion clsx tailwind-merge2
Add util file
lib/util.ts
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
3
Copy and paste the following code into your project
advanced-loading-spinner.tsx
"use client";
import { motion } from "motion/react";
import { useEffect, useState } from "react";
import { cn } from "@/lib/utils";
type AdvancedLoadingSpinnerProps = {
size?: number;
speed?: number;
};
const AdvancedLoadingSpinner = ({
size = 60,
speed = 1,
}: AdvancedLoadingSpinnerProps) => {
const [phase, setPhase] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setPhase((prev) => (prev + 1) % 4);
}, 2000 / speed);
return () => clearInterval(interval);
}, [speed]);
const colors = [
"text-blue-500",
"text-green-500",
"text-purple-500",
"text-orange-500",
];
return (
<div className="relative flex items-center justify-center" style={{ width: size, height: size }}>
{/* Outer ring */}
<motion.div
className={cn("absolute rounded-full border-4 border-transparent", colors[phase])}
style={{
width: size,
height: size,
borderTopColor: "currentColor",
}}
animate={{ rotate: 360 }}
transition={{
duration: 1 / speed,
repeat: Infinity,
ease: "linear",
}}
/>
{/* Middle ring */}
<motion.div
className={cn("absolute rounded-full border-2 border-transparent", colors[(phase + 1) % 4])}
style={{
width: size * 0.7,
height: size * 0.7,
borderRightColor: "currentColor",
}}
animate={{ rotate: -360 }}
transition={{
duration: 1.5 / speed,
repeat: Infinity,
ease: "linear",
}}
/>
{/* Inner dot */}
<motion.div
className={cn("absolute rounded-full", colors[(phase + 2) % 4])}
style={{
width: size * 0.2,
height: size * 0.2,
}}
animate={{
scale: [1, 1.2, 1],
opacity: [0.5, 1, 0.5],
}}
transition={{
duration: 1 / speed,
repeat: Infinity,
ease: "easeInOut",
}}
/>
{/* Orbital dots */}
{[0, 1, 2].map((index) => (
<motion.div
key={index}
className={cn("absolute rounded-full", colors[(phase + 3) % 4])}
style={{
width: size * 0.1,
height: size * 0.1,
}}
animate={{
rotate: 360,
x: Math.cos((index * 120 * Math.PI) / 180) * (size * 0.3),
y: Math.sin((index * 120 * Math.PI) / 180) * (size * 0.3),
}}
transition={{
duration: 2 / speed,
repeat: Infinity,
ease: "linear",
delay: (index * 0.3) / speed,
}}
/>
))}
</div>
);
};
export default AdvancedLoadingSpinner;"use client";
import { motion } from "motion/react";
import { useEffect, useState } from "react";
import { cn } from "@/lib/utils";
type AdvancedLoadingSpinnerProps = {
size?: number;
speed?: number;
};
const AdvancedLoadingSpinner = ({
size = 60,
speed = 1,
}: AdvancedLoadingSpinnerProps) => {
const [phase, setPhase] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setPhase((prev) => (prev + 1) % 4);
}, 2000 / speed);
return () => clearInterval(interval);
}, [speed]);
const colors = [
"text-blue-500",
"text-green-500",
"text-purple-500",
"text-orange-500",
];
return (
<div className="relative flex items-center justify-center" style={{ width: size, height: size }}>
{/* Outer ring */}
<motion.div
className={cn("absolute rounded-full border-4 border-transparent", colors[phase])}
style={{
width: size,
height: size,
borderTopColor: "currentColor",
}}
animate={{ rotate: 360 }}
transition={{
duration: 1 / speed,
repeat: Infinity,
ease: "linear",
}}
/>
{/* Middle ring */}
<motion.div
className={cn("absolute rounded-full border-2 border-transparent", colors[(phase + 1) % 4])}
style={{
width: size * 0.7,
height: size * 0.7,
borderRightColor: "currentColor",
}}
animate={{ rotate: -360 }}
transition={{
duration: 1.5 / speed,
repeat: Infinity,
ease: "linear",
}}
/>
{/* Inner dot */}
<motion.div
className={cn("absolute rounded-full", colors[(phase + 2) % 4])}
style={{
width: size * 0.2,
height: size * 0.2,
}}
animate={{
scale: [1, 1.2, 1],
opacity: [0.5, 1, 0.5],
}}
transition={{
duration: 1 / speed,
repeat: Infinity,
ease: "easeInOut",
}}
/>
{/* Orbital dots */}
{[0, 1, 2].map((index) => (
<motion.div
key={index}
className={cn("absolute rounded-full", colors[(phase + 3) % 4])}
style={{
width: size * 0.1,
height: size * 0.1,
}}
animate={{
rotate: 360,
x: Math.cos((index * 120 * Math.PI) / 180) * (size * 0.3),
y: Math.sin((index * 120 * Math.PI) / 180) * (size * 0.3),
}}
transition={{
duration: 2 / speed,
repeat: Infinity,
ease: "linear",
delay: (index * 0.3) / speed,
}}
/>
))}
</div>
);
};
export default AdvancedLoadingSpinner;4
Update the import paths to match your project setup
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| size | number | 60 | The size of the loading spinner in pixels. |
| speed | number | 1 | The animation speed multiplier. |