Launched best Saas Marketing template at cheap — Check out

Circular Text

Circular text animations create a circular text effect that continuously rotates. This engaging animation can be used to draw attention to important content or add visual interest to your design.

Installation

npm install framer-motion
pnpm install framer-motion
yarn add framer-motion
bun add framer-motion
'use client';
import {
  motion,
  MotionValue,
  Transition,
  useAnimation,
  useMotionValue,
} from 'framer-motion';
import { useEffect } from 'react';

type CircularTextProps = {
  text: string;
  spinDuration?: number;
  onHover?: 'slowDown' | 'speedUp' | 'pause' | 'goBonkers';
  className?: string;
};

const getRotationTransition = (
  duration: number,
  from: number,
  loop: boolean = true,
) => ({
  from,
  to: from + 360,
  ease: 'linear' as const,
  duration,
  type: 'tween' as const,
  repeat: loop ? Infinity : 0,
});

const getTransition = (duration: number, from: number) => ({
  rotate: getRotationTransition(duration, from),
  scale: {
    type: 'spring' as const,
    damping: 20,
    stiffness: 300,
  },
});

export function CircularText({
  text = 'Circular Text Animation • ',
  spinDuration = 20,
  onHover = 'speedUp',
  className = '',
}: Readonly<CircularTextProps>) {
  const letters = Array.from(text);
  const controls = useAnimation();
  const rotation: MotionValue<number> = useMotionValue(0);

  useEffect(() => {
    const start = rotation.get();
    controls.start({
      rotate: start + 360,
      scale: 1,
      transition: getTransition(spinDuration, start),
    });
  }, [spinDuration, text, onHover, controls]);

  const handleHoverStart = () => {
    const start = rotation.get();

    if (!onHover) return;

    let transitionConfig: ReturnType<typeof getTransition> | Transition;
    let scaleVal = 1;

    switch (onHover) {
      case 'slowDown':
        transitionConfig = getTransition(spinDuration * 2, start);
        break;
      case 'speedUp':
        transitionConfig = getTransition(spinDuration / 4, start);
        break;
      case 'pause':
        transitionConfig = {
          rotate: { type: 'spring', damping: 20, stiffness: 300 },
          scale: { type: 'spring', damping: 20, stiffness: 300 },
        };
        break;
      case 'goBonkers':
        transitionConfig = getTransition(spinDuration / 20, start);
        scaleVal = 0.8;
        break;
      default:
        transitionConfig = getTransition(spinDuration, start);
    }

    controls.start({
      rotate: start + 360,
      scale: scaleVal,
      transition: transitionConfig,
    });
  };

  const handleHoverEnd = () => {
    const start = rotation.get();
    controls.start({
      rotate: start + 360,
      scale: 1,
      transition: getTransition(spinDuration, start),
    });
  };

  return (
    <div className="flex min-h-[400px] items-center justify-center rounded-lg">
      <motion.div
        className={`relative m-0 mx-auto h-[200px] w-[200px] origin-center cursor-pointer rounded-full text-center font-black text-white ${className}`}
        style={{ rotate: rotation }}
        initial={{ rotate: 0 }}
        animate={controls}
        onMouseEnter={handleHoverStart}
        onMouseLeave={handleHoverEnd}
      >
        {letters.map((letter, i) => {
          const rotationDeg = (360 / letters.length) * i;
          const factor = Math.PI / letters.length;
          const x = factor * i;
          const y = factor * i;
          const transform = `rotateZ(${rotationDeg}deg) translate3d(${x}px, ${y}px, 0)`;

          return (
            <span
              key={i}
              className="absolute inset-0 inline-block text-2xl transition-all duration-500 ease-out"
              style={{ transform, WebkitTransform: transform }}
            >
              {letter}
            </span>
          );
        })}
      </motion.div>
    </div>
  );
}

export default CircularText;

Usage

import { CircularText } from "@/components/mvpblocks/text-animations/circular-text";

export default function MyComponent() {
  return (
    <div className="flex items-center justify-center h-screen bg-black">
      <CircularText
        text="Circular Text Animation • "
        spinDuration={20}
        onHover="speedUp"
        className="text-white"
      />
    </div>
  );
}

API

PropTypeDefault
text?
string
-
spinDuration?
number
20
onHover?
"slowDown" | "speedUp" | "pause" | "goBonkers"
"speedUp"
className?
string
-

Features

  • 🎯 Smooth Animation: Continuous rotation with customizable speed
  • 🎨 Hover Effects: Multiple hover behaviors (slowDown, speedUp, pause, goBonkers)
  • 🎪 Customizable: Easy to style with CSS classes
  • 📱 Responsive: Works well on different screen sizes
  • Performance: Optimized with framer-motion

Examples

Speed Up on Hover

<CircularText
  text="Speed Up Effect • "
  spinDuration={15}
  onHover="speedUp"
/>

Pause on Hover

<CircularText
  text="Pause on Hover • "
  spinDuration={25}
  onHover="pause"
/>

Go Bonkers on Hover

<CircularText
  text="Go Bonkers! • "
  spinDuration={30}
  onHover="goBonkers"
/>