Text Loader

An animated text loader component with gradient reveals and smooth GSAP animations. Features customizable colors, timing, and responsive design with Google Fonts integration.

React
Tailwind CSS
GSAP
Animation
Typography
Loader

Using CLI

npx dimaac add TextLoader

Manual Installation

npm install react @gsap/react

lib/utils.ts

import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
} 

components/ui/TextLoader.tsx

'use client';

import { useRef } from 'react';
import { gsap } from 'gsap';
import { useGSAP } from '@gsap/react';
import { cn } from '@/lib/utils';

interface TextLoaderProps {
  text?: string;
  className?: string;
  onComplete?: () => void;
  gradientColors?: string[];
  backgroundColor?: string;
  duration?: {
    slideUp?: number;
    reveal?: number;
    slideDown?: number;
  };
  delays?: {
    stagger?: number;
    betweenAnimations?: number;
    beforeSlideDown?: number;
  };
}

const TextLoader: React.FC<TextLoaderProps> = ({
  text = 'DIMAAC',
  className = '',
  onComplete,
  gradientColors = ['#ff0000', '#ff3333', '#ff6600', '#cc0000'],
  backgroundColor = '#111',
  duration = {
    slideUp: 0.6,
    reveal: 0.8,
    slideDown: 0.6,
  },
  delays = {
    stagger: 0.05,
    betweenAnimations: 0.3,
    beforeSlideDown: 0.5,
  },
}) => {
  const containerRef = useRef<HTMLDivElement>(null);
  const textRef = useRef<HTMLDivElement>(null);

  useGSAP(() => {
    if (!textRef.current) return;

    const letters = textRef.current.querySelectorAll('.letter');
    const tl = gsap.timeline({
      onComplete: () => {
        onComplete?.();
      }
    });

    // Set initial state
    gsap.set(letters, {
      y: 100,
      '--clipPath': 'inset(100% 0 0 0)',
    });

    // Animation sequence
    tl.to(letters, {
      duration: duration.slideUp,
      y: 0,
      stagger: delays.stagger,
      ease: 'power2.out',
    })
    .to(letters, {
      '--clipPath': 'inset(0% 0 0 0)',
      duration: duration.reveal,
      delay: delays.betweenAnimations,
      ease: 'power1.inOut',
    })
    .to(letters, {
      duration: duration.slideDown,
      y: -100,
      stagger: delays.stagger,
      delay: delays.beforeSlideDown,
      ease: 'power2.in',
    })
    .to(containerRef.current, {
      y: '-100%',
      duration: 0.8,
      ease: 'power2.inOut',
      delay: 0.3,
    }, '-=0.2');
  }, { scope: containerRef, dependencies: [text, duration, delays, onComplete] });

  const gradientString = `linear-gradient(45deg, ${gradientColors.join(', ')})`;

  return (
    <div 
      ref={containerRef}
      className={cn("fixed inset-0 flex items-center justify-center overflow-hidden z-50", className)}
      style={{ backgroundColor }}
    >
      <div 
        ref={textRef}
        className="text-container flex relative overflow-hidden"
        style={{
          fontFamily: '"Oswald", "Bebas Neue", sans-serif',
          fontSize: 'clamp(3rem, 12vw, 6.5rem)',
          fontWeight: 700,
          lineHeight: 1,
        }}
      >
        {text.split('').map((char, index) => (
          <span
            key={`${char}-${index}`}
            className="letter inline-block relative"
            data-text={char}
            style={{
              color: 'rgba(255, 255, 255, 0.2)',
              transform: 'translateY(100px)',
              '--clipPath': 'inset(100% 0 0 0)',
            } as React.CSSProperties}
          >
            {char}
            <span
              className="absolute left-0 top-0 w-full h-full"
              style={{
                content: `"${char}"`,
                backgroundImage: gradientString,
                backgroundClip: 'text',
                WebkitBackgroundClip: 'text',
                WebkitTextFillColor: 'transparent',
                clipPath: 'var(--clipPath)',
                WebkitClipPath: 'var(--clipPath)',
                transition: 'clip-path 0s',
              }}
            >
              {char}
            </span>
          </span>
        ))}
      </div>

      <style jsx>{`
        @import url('https://fonts.googleapis.com/css2?family=Oswald:wght@700&display=swap');
        
        .text-container {
          /* Responsive font sizing */
          font-size: clamp(2.5rem, 8vw, 6.5rem);
        }
        
        @media (max-width: 640px) {
          .text-container {
            font-size: clamp(2rem, 10vw, 4rem);
          }
        }
        
        @media (max-width: 480px) {
          .text-container {
            font-size: clamp(1.5rem, 12vw, 3rem);
          }
        }
        
        @media (max-width: 360px) {
          .text-container {
            font-size: clamp(1.2rem, 14vw, 2.5rem);
          }
        }
        
        /* Ensure proper spacing on mobile */
        @media (max-width: 768px) {
          .letter {
            letter-spacing: -0.02em;
          }
        }
      `}</style>
    </div>
  );
};

export default TextLoader;