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 items-center justify-center min-h-[400px]  rounded-lg">
      <motion.div
        className={`m-0 mx-auto rounded-full w-[200px] h-[200px] relative font-black text-white text-center cursor-pointer origin-center ${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 inline-block inset-0 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"
/>

Last updated on