Docs

Orbiting Items 3D

List component with orbiting items. The items orbit around the center of an element in 3D Ellipse.

Installation

Install dependencies

npm install lucide-react

Update tailwind.config.js

Add the following to your tailwind.config.js file.

module.exports = {
  theme: {
    extend: {
      keyframes: {
        float: {
          '0%, 100%': { transform: 'translateY(0)' },
          '50%': { transform: 'translateY(-40px)' },
        },
      },
      animation: {
        float: 'float 3s ease-in-out infinite',
      },
    }
  }
}

Run the following command

It will create a new file orbiting-items-3-d.tsx inside the components/animata/list directory.

mkdir -p components/animata/list && touch components/animata/list/orbiting-items-3-d.tsx

Paste the code

Open the newly created file and paste the following code:

import { useEffect, useState } from "react";
import { Apple, BadgeCent, BadgeInfo, BadgeX, Banana, Bolt } from "lucide-react";
 
import { Icons } from "@/components/icons";
import { cn } from "@/lib/utils";
 
export const CenterIcon = (
  <Icons.logo
    className="center z-0 h-32 w-32 animate-float rounded-full bg-gradient-to-r from-purple-400 to-blue-400 shadow-lg"
    style={{
      boxShadow: "0 0 20px 10px rgba(128, 90, 213, 0.6)",
    }}
  />
);
export const LucideIcons = [
  <Banana key="banana" className="h-12 w-12" />,
  <Bolt key="bolt" className="h-12 w-12" />,
  <BadgeX key="badge-x" className="h-12 w-12" />,
  <BadgeCent key="badge-cent" className="h-12 w-12" />,
  <BadgeInfo key="badge-info" className="h-12 w-12" />,
  <Apple key="apple" className="h-12 w-12" />,
];
 
interface OrbitingItems3DProps {
  /**
   * The radius of the ellipse on X-axis in percentage, relative to the container.
   */
  radiusX: number;
 
  /**
   * The radius of the ellipse on Y-axis in percentage, relative to the container.
   */
  radiusY: number;
 
  /**
   * The angle at which ellipse is tilted to x-axis.
   */
  tiltAngle: number;
 
  /**
   * The time taken for the revolution around the center element.
   */
  duration: number;
 
  /**
   * The items to orbit around the center of the parent element.
   */
  items: React.ReactNode[];
 
  /**
   * Class name for the background element.
   */
  backgroundClassName?: string;
 
  /**
   * Class name for the container element.
   */
  containerClassName?: string;
 
  /**
   * Additional classes for the item container.
   */
  className?: string;
}
 
export default function OrbitingItems3D({
  radiusX = 120,
  radiusY = 30,
  tiltAngle = 360 - 30,
  duration = 25,
  items = LucideIcons,
  backgroundClassName,
  containerClassName,
  className,
}: OrbitingItems3DProps) {
  // The OrbitingItems3D component creates an animated elliptical orbiting effect for a set of items around a central element.
  // It allows for a visually dynamic layout, where items revolve around the center in a smooth, continuous motion,
  // creating the illusion of 3D movement. The component provides a range of customizable options to control the orbit,
  // including the size of the elliptical path, tilt angle, and animation duration.
 
  const CalculateItemStyle = ({
    index,
    radiusX,
    radiusY,
    totalItems,
    tiltAngle,
    duration,
  }: {
    index: number;
    radiusX: number;
    radiusY: number;
    totalItems: number;
    tiltAngle: number;
    duration: number;
  }) => {
    const angleStep = 360 / totalItems;
    const [angle, setAngle] = useState(index * angleStep);
    useEffect(() => {
      const animation = setInterval(() => {
        setAngle((prevAngle) => (prevAngle + 1) % 360);
      }, duration);
 
      return () => clearInterval(animation);
    }, [duration]);
    // Calculate the current angle for the item on the orbit
 
    const radians = (angle * Math.PI) / 180;
 
    // X and Y positions before tilt
    const x = radiusX * Math.cos(radians);
    const y = radiusY * Math.sin(radians);
 
    // Apply the tilt using rotation matrix
    const tiltRadians = (tiltAngle * Math.PI) / 180;
    const xTilted = x * Math.cos(tiltRadians) - y * Math.sin(tiltRadians);
    const yTilted = x * Math.sin(tiltRadians) + y * Math.cos(tiltRadians);
    const zIndex = angle > 180 ? -1 : 1;
    const scale = angle < 180 ? 1.2 : 1.0;
 
    return {
      left: `${50 + xTilted}%`,
      top: `${50 + yTilted}%`,
      transform: `translate(-50%, -50%) scale(${scale})`,
      zIndex: zIndex,
      transition: "transform 0.8s ease-in-out",
    };
  };
 
  const reverse = cn("transition-transform ease-linear direction-reverse repeat-infinite");
 
  return (
    <div
      className={cn(
        "storybook-fix group flex items-center justify-center py-32",
        containerClassName,
      )}
    >
      <div
        className={cn(
          "absolute inset-0 -z-10 h-full w-full items-center bg-gradient-to-r from-violet-200 to-pink-200",
          backgroundClassName,
        )}
      />
      <div
        className={cn(
          "relative flex h-64 w-64 items-center justify-center ease-linear repeat-infinite",
          className,
        )}
      >
        {CenterIcon}
        {items.map((item, index) => {
          return (
            <div
              key={index}
              className="absolute flex h-20 w-20 items-center justify-center rounded-full bg-white/30 shadow-xl shadow-purple-500/30 backdrop-blur-md transition-transform duration-500 ease-out"
              style={CalculateItemStyle({
                index,
                radiusX,
                radiusY,
                tiltAngle,
                totalItems: items.length,
                duration,
              })}
            >
              <div className={reverse}>{item}</div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

Credits

Built by Vishal