Card Stack Animation

A scroll-triggered card stack animation component that displays cards fanning out and animating in from below.

Installation

pnpm dlx shadcn@latest add https://ui.nexvyn.dev/r/styles/new-york-v4/cards.json
Install dependencies:
npm install gsap @gsap/react
Copy the component:
cards.tsx
"use client"

import { useRef } from "react"
import { useGSAP } from "@gsap/react"
import gsap from "gsap"
import { ScrollTrigger } from "gsap/ScrollTrigger"

gsap.registerPlugin(ScrollTrigger)

const CardStackAnimate = () => {
  const first = useRef(null)

  useGSAP(() => {
    const tl = gsap.timeline({})
    const cards = gsap.utils.toArray(".card")

    tl.from(cards, {
      y: "100vh",
      stagger: 1,
      ease: "power3.out",
      duration: 1,
      scrollTrigger: {
        trigger: first.current,
        start: "top top",
        end: "2000",
        scrub: 1,
        pin: true,
      },
    })
  })

  const colors = [
    { name: "Pink", class: "bg-pink-300", link: "/card_animation/pink.png" },
    { name: "Blue", class: "bg-blue-300", link: "/card_animation/blue.png" },
    {
      name: "Yellow",
      class: "bg-yellow-200",
      link: "/card_animation/yellow.png",
    },
    { name: "Green", class: "bg-green-200", link: "/card_animation/green.png" },
    {
      name: "Purple",
      class: "bg-purple-200",
      link: "/card_animation/purple.png",
    },
    {
      name: "Orange",
      class: "bg-orange-200",
      link: "/card_animation/orange.png",
    },
  ]

  return (
    <div className="h-full w-full bg-black">
      <div
        ref={first}
        className="relative flex h-screen w-full items-center justify-center bg-black"
      >
        <div className="text-9xl font-light text-white">HEY GSAPPPPP</div>
        {colors.map((color, i) => (
          <div
            key={i}
            className={`card absolute h-[400px] w-[300px] rounded-3xl shadow-lg ${color.class} overflow-hidden p-4`}
            style={{
              left: `calc(50% + (${i * 70}px - ${(colors.length / 2) * 70}px))`,
              transform: `translateX(-50%) rotate(${(i - colors.length / 2) * 10}deg)`,
            }}
          >
            <div className="flex justify-between">
              <div className="text-xs text-neutral-700">Card {i + 1}</div>
              <div className="text-xs text-neutral-700">Color {color.class}</div>
            </div>
            <img src={color.link} alt={color.name} className="absolute left-0" />
          </div>
        ))}
      </div>
      <div className="flex h-screen w-full items-center justify-center">hi</div>
    </div>
  )
}

export default CardStackAnimate
Update imports to match your project structure.

Usage

import CardStackAnimate from "@/components/ui/cards"
<CardStackAnimate />

Features

  • Scroll-triggered animations - Cards animate based on scroll position
  • Staggered entrance - Each card enters sequentially for a polished effect
  • Pin effect - Scroll triggers pinning for controlled animation timing
  • Customizable colors - Easy color variants for each card
  • Responsive layout - Cards fan out dynamically based on container

Customization

Adding More Colors

Extend the colors array to add more card variants:

const colors = [
  { name: "Red", class: "bg-red-300", link: "/card_animation/red.png" },
  { name: "Teal", class: "bg-teal-300", link: "/card_animation/teal.png" },
  // ... add more colors
]

Adjusting Animation Speed

Modify the duration and stagger values:

tl.from(cards, {
  y: "100vh",
  stagger: 0.5, // Reduce for faster stagger
  duration: 2, // Increase for slower animation
  ease: "power3.out",
  // ...
})

Changing Scroll Trigger Duration

Adjust the end value to control how long the animation scrubs:

scrollTrigger: {
  trigger: first.current,
  start: "top top",
  end: "3000",        // Increase for longer scroll distance
  scrub: 1,
  pin: true,
}

Customizing Card Dimensions

Update the size classes on the card div:

className={`card absolute h-[500px] w-[400px] rounded-3xl shadow-lg ...`}