All components

Animated GIF Text

Animated GIF Text is a React and Tailwind CSS text component from Cult UI, licensed MIT. Copy it and paste it into your project.

Cult UI MIT text

What it is

Animated GIF Text is a React and Tailwind CSS text component from Cult UI, licensed MIT. Copy it and paste it into your project.

How to use it

Open it in the catalogue, press copy, and paste it into your project. The prompt you copy carries the code and the rules that tell your agent to reproduce it without changing anything.

Open in the catalogue

Five free copies a month. No card.

Needs

npm i class-variance-authority next

Licence

This component comes from Cult UI and is redistributed under the MIT licence, which permits commercial use. The copyright notice travels with the code.

Animated GIF Text

apps/www/registry/default/ui/text-gif.tsx
"use client"

import React, { useEffect, useMemo, useState, type CSSProperties } from "react"
import Image from "next/image"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

// Define text style variants
const textBaseVariants = cva("", {
  variants: {
    size: {
      default: "text-2xl sm:text-3xl lg:text-4xl",
      xxs: "text-base sm:text-lg lg:text-lg",
      xs: "text-lg sm:text-xl lg:text-2xl",
      sm: "text-xl sm:text-2xl lg:text-3xl",
      md: "text-2xl sm:text-3xl lg:text-4xl",
      lg: "text-3xl sm:text-4xl lg:text-5xl",
      xl: "text-4xl sm:text-5xl lg:text-6xl",
      xxl: "text-[2.5rem] sm:text-6xl lg:text-[6rem]",
      xll: "text-5xl sm:text-6xl lg:text-[7rem]",
      xxxl: "text-[6rem] leading-5 lg:leading-8 sm:text-6xl lg:text-[8rem]",
    },
    weight: {
      default: "font-bold",
      thin: "font-thin",
      base: "font-base",
      semi: "font-semibold",
      bold: "font-bold",
      black: "font-black",
    },
    font: {
      default: "font-sansTight",
      serif: "font-serif",
      mono: "font-mono",
    },
  },
  defaultVariants: {
    size: "default",
    weight: "bold",
    font: "default",
  },
})

interface TextGifProps extends VariantProps<typeof textBaseVariants> {
  gifUrl: string
  text: string
  className?: string
  fallbackColor?: string
  transitionDuration?: number
}

const TextGif = React.memo(function TextGifComponent({
  gifUrl,
  text,
  size,
  weight,
  font,
  className,
  fallbackColor = "black",
  transitionDuration = 300,
}: TextGifProps) {
  const [loaded, setLoaded] = useState(false)
  const [error, setError] = useState(false)

  // Reset states when gifUrl changes
  useEffect(() => {
    setLoaded(false)
    setError(false)
  }, [gifUrl])

  // Memoize className for performance
  const textClassName = useMemo(
    () =>
      cn(
        textBaseVariants({ size, weight, font }),
        loaded && !error ? "text-transparent bg-clip-text" : "",
        className,
        "pb-1.5 md:pb-4"
      ),
    [size, weight, font, className, loaded, error]
  )

  // Memoize style for performance
  const textStyle = useMemo(() => {
    const style: CSSProperties = {
      backgroundSize: "cover",
      backgroundPosition: "center",
      backgroundRepeat: "no-repeat",
      WebkitBackgroundClip: "text",
      lineHeight: 1,
      textAlign: "center",
      color: fallbackColor, // Always set the fallback color initially
      WebkitTextFillColor: fallbackColor, // Safari fix
      transition: `background-image ${transitionDuration}ms ease-in-out, color ${transitionDuration}ms ease-in-out`,
    }

    if (loaded && !error) {
      style.backgroundImage = `url(${gifUrl})`
      style.color = "transparent"
      style.WebkitTextFillColor = "transparent" // Safari fix
    }

    return style
  }, [loaded, error, gifUrl, transitionDuration, fallbackColor])

  return (
    <div className="relative inline-block">
      {/* Hidden image for preloading */}
      {gifUrl && (
        <Image
          src={gifUrl || "/placeholder.svg"}
          alt=""
          width={1}
          height={1}
          className="absolute opacity-0 pointer-events-none"
          onLoad={() => {
            setLoaded(true)
            setError(false)
          }}
          onError={() => {
            setError(true)
            setLoaded(false)
          }}
          priority
          unoptimized
        />
      )}
      <span className={textClassName} style={textStyle}>
        {text}
      </span>
    </div>
  )
})

// Export common GIF URLs
const gifUrls = [
  "https://media.giphy.com/media/3zvbrvbRe7wxBofOBI/giphy.gif",
  "https://media.giphy.com/media/fnglNFjBGiyAFtm6ke/giphy.gif",
  "https://media.giphy.com/media/9Pmfazv34l7aNIKK05/giphy.gif",
  "https://media.giphy.com/media/4bhs1boql4XVJgmm4H/giphy.gif",
]

// Optional: Preloader component
function PreloadGifs() {
  return (
    <div className="hidden">
      {gifUrls.map((url) => (
        <Image
          key={url}
          src={url}
          alt=""
          width={1}
          height={1}
          priority
          unoptimized
        />
      ))}
    </div>
  )
}

export { TextGif }
export default TextGif

Similar components