ComponentsChangelogPro

    Getting Started

    Animated Cards

    Components

    Backgrounds

    3d & Shaders

    Text Animations

    Buttons

    Docs
    Cloud Orbit

    Cloud Orbit

    This component creates a dynamic and interactive experience, where each icon orbits in a fluid motion. You can customize the speed, size, and duration of the animations for both the central element and the orbiting icons.

    Loading...

    Installation

    Install dependencies

    npm install

    npm install clsx tailwind-merge framer-motion

    Add util file

    lib/utils.ts

    import { ClassValue, clsx } from "clsx";
    import { twMerge } from "tailwind-merge";
     
    export function cn(...inputs: ClassValue[]) {
      return twMerge(clsx(inputs));
    }

    Add the following code in your tailwind.config.js file

    tailwind.config.js

    /** @type {import('tailwindcss').Config} */
    module.exports = {
      theme: {
        extend: {
          animation: {
            "cloud-orbit": "cloud-orbit calc(var(--speed)*1s) linear infinite",
          },
          keyframes: {
            "cloud-orbit": {
              "0%": {
                transform:
                  "rotate(0deg) translateY(calc(var(--radius) * 1px)) rotate(0deg)",
              },
              "25%": {
                transform:
                  "rotate(90deg) translateY(calc(var(--radius) * 1px)) rotate(-90deg)",
              },
              "50%": {
                transform:
                  "rotate(180deg) translateY(calc(var(--radius) * 1px)) rotate(-180deg)",
              },
              "75%": {
                transform:
                  "rotate(270deg) translateY(calc(var(--radius) * 1px)) rotate(-270deg)",
              },
              "100%": {
                transform:
                  "rotate(360deg) translateY(calc(var(--radius) * 1px)) rotate(-360deg)",
              },
            },
          },
        },
      },
    };

    Copy the source code

    cloud-orbit.tsx

    "use client";
     
    import * as React from "react";
    import { motion, AnimatePresence } from "framer-motion";
    import { cn } from "@/lib/utils";
     
    interface Image {
      url: string;
      name: string;
    }
     
    interface CloudOrbitProps {
      duration?: number;
      children?: React.ReactNode;
      size?: number;
      className?: string;
      images?: Image[];
      [key: string]: any;
    }
     
    export function CloudOrbit({
      duration = 3,
      children,
      size = 160,
      className,
      images = [],
      ...props
    }: CloudOrbitProps) {
      const [currentIndex, setCurrentIndex] = React.useState(0);
      const lastTimestamp = React.useRef(0);
     
      React.useEffect(() => {
        let animationFrameId: number;
     
        const updateFrame = (timestamp: number) => {
          if (lastTimestamp.current === 0) {
            lastTimestamp.current = timestamp;
          }
     
          const elapsedTime = (timestamp - lastTimestamp.current) / 1000;
          const currentImageIndex = Math.floor(elapsedTime / duration) % images.length;
     
          setCurrentIndex(currentImageIndex);
     
          animationFrameId = requestAnimationFrame(updateFrame);
        };
     
        if (images.length > 0) {
          animationFrameId = requestAnimationFrame(updateFrame);
        }
     
        return () => cancelAnimationFrame(animationFrameId);
      }, [duration, images.length]);
     
      return (
        <div
          style={{
            "--size": `${size}px`,
          } as React.CSSProperties}
          className={cn(
            "relative h-[--size] w-[--size] select-none rounded-full flex items-center justify-center bg-black/5 dark:bg-white/10",
            className
          )}
          {...props}
        >
          <AnimatePresence>
            {images.length > 0 &&
              images.map(
                (image, index) =>
                  index === currentIndex && (
                    <motion.img
                      key={image.url}
                      src={image.url}
                      alt={image.name}
                      initial={{ opacity: 0, scale: 0.8 }}
                      animate={{ opacity: 1, scale: [0.8, 1] }}
                      exit={{ opacity: 0, scale: [1, 0.8] }}
                      transition={{
                        type: "spring",
                        stiffness: 200,
                        damping: 25,
                      }}
                      className={cn(
                        "absolute h-[--size] w-[--size] z-10 rounded-[inherit]",
                        className
                      )}
                    />
                  )
              )}
          </AnimatePresence>
          {children}
        </div>
      );
    }
     
    interface OrbitingImageProps {
      speed?: number;
      radius?: number;
      startAt?: number;
      size?: number;
      className?: string;
      images?: Image[];
      duration?: number;
      [key: string]: any;
    }
     
    export function OrbitingImage({
      speed = 20,
      radius = 100,
      startAt = 0,
      size = 80,
      className,
      images = [],
      duration = 3,
      ...props
    }: OrbitingImageProps) {
      const [currentIndex, setCurrentIndex] = React.useState(0);
      const lastTimestamp = React.useRef(0);
     
      React.useEffect(() => {
        let animationFrameId: number;
     
        const updateFrame = (timestamp: number) => {
          if (lastTimestamp.current === 0) {
            lastTimestamp.current = timestamp;
          }
     
          const elapsedTime = (timestamp - lastTimestamp.current) / 1000;
          const currentImageIndex = Math.floor(elapsedTime / duration) % images.length;
     
          setCurrentIndex(currentImageIndex);
     
          animationFrameId = requestAnimationFrame(updateFrame);
        };
     
        if (images.length > 0) {
          animationFrameId = requestAnimationFrame(updateFrame);
        }
     
        return () => cancelAnimationFrame(animationFrameId);
      }, [duration, images.length]);
     
      return (
        <div
          style={{
            "--speed": speed,
            "--radius": radius,
            "--size": `${size}px`,
            animationDelay: `-${startAt * speed}s`,
          } as React.CSSProperties}
          className={cn(
            "absolute pointer-events-none h-[--size] w-[--size] z-[5] transform-gpu animate-cloud-orbit items-center justify-center rounded-full ",
            className
          )}
          {...props}
        >
          <AnimatePresence>
            {images.length > 0 &&
              images.map(
                (image, index) =>
                  index === currentIndex && (
                    <motion.div
                      key={image.url}
                      style={{
                        width: `${size}px`,
                        height: `${size}px`,
                        position: "absolute",
                        transformOrigin: `center ${radius}px`,
                      }}
                      initial={{ opacity: 0, scale: 0.8 }}
                      animate={{ opacity: 1, scale: [0.8, 1] }}
                      exit={{ opacity: 0, scale: [1, 0.8] }}
                      transition={{
                        type: "spring",
                        stiffness: 200,
                        damping: 25,
                      }}
                      className={cn(
                        "rounded-full bg-black/5 dark:bg-white/10 p-[10%]",
                        className
                      )}
                    >
                      <img
                        src={image.url}
                        alt={image.name}
                        className="h-full w-full flex items-center justify-center rounded-full object-contain"
                      />
                    </motion.div>
                  )
              )}
          </AnimatePresence>
        </div>
      );
    }

    Props

    cloud-orebit props

    PropTypeDescriptionDefault
    durationnumberThe duration in seconds for each image to stay visible in the orbit animation3
    childrenReact.ReactNodeOptional content to display in the center of the orbit-
    sizenumberThe size of the orbit container (in pixels)160
    classNamestringAdditional CSS classes to apply to the container-
    imagesArray<{ url: string; name: string }>An array of image objects to display in the orbit animation[]

    orbiting-image props

    PropTypeDescriptionDefault
    speednumberThe speed of the orbit animation20
    radiusnumberThe radius of the orbit path (in pixels)100
    startAtnumberStart time offset for the orbit animation (in seconds)0
    sizenumberThe size of each orbiting image (in pixels)80
    classNamestringAdditional CSS classes to apply to the orbiting image container-
    imagesArray<{ url: string; name: string }>An array of image objects to display in the orbit animation[]
    durationnumberThe duration in seconds for each image to stay visible in the orbit animation3

    Credits

  1. This component is inspired by Icloud
  2. The images are from Spline.One