# Scroll Stack Cards

> Full-width panels that stack over one another as you scroll, each one locking into place with an overshoot while the cards beneath compress into a visible deck.

Canonical: https://gsapvault.com/effects/scroll-stack-cards
Live demo: https://gsapvault.com/demos/scroll-stack-cards/index.html

| Property | Value |
|----------|-------|
| Type | effect |
| Tier | paid |
| Price | £5 |
| Difficulty | advanced |
| Plugins | ScrollTrigger |
| Techniques | scroll-stack, pinning, scrub, velocity, stagger |
| Uses Lenis | Yes |

## Overview

A scroll-stacking card effect that builds a visible deck instead of scrolling content past. Each panel rides up from below a pinned frame, overshoots its resting line by a few pixels and locks, while every card already down slides back a notch, scales, dims and leans away one level at a time.

All of it comes off a single scrubbed ScrollTrigger timeline, so the whole deck is scrubbable in both directions with no state to keep in sync. Scroll velocity adds a second layer: a hard wheel flick tips the entire pile back, drags it behind the gesture and squashes it, then springs it level again.

A corner chip counts the cards as they lock, and every distance is measured through functions with invalidateOnRefresh so a resize or a mobile address bar re-fits the deck rather than drifting.

## Features

- Builds a visible deck of panels in one pinned section, driven entirely by scroll position
- Locking overshoot on every arrival: the card rides a few pixels past its resting line, then relaxes onto it
- Depth compression staggered level by level, so the pile settles like a stack of cards rather than one block
- Three depth channels off one number: scale step, dim overlay and a backward lean per level of depth
- Velocity lean that tips, drags and squashes the whole deck on a fast flick, eased through gsap.quickTo and always released back to level
- Live counter and progress rail fed from the scrubbed timeline, so the count flips on the frame a card visibly locks
- Depth cap and reduced offsets on coarse pointers, so five cards still read as a deck at 375px
- Reduced motion and no-JS both fall back to the plain vertical column the markup already is, with nothing hidden
- Configurable through data attributes: peek offset, scale step, dim step, depth cap, scroll length, velocity lean on or off
- Pins below a fixed page bar by measuring it live, and re-measures every distance on ScrollTrigger refresh

## Use Cases

- Product feature walkthroughs where each panel needs to be read, not scrolled past
- Agency service or process sections that should feel like a deck being dealt
- Pricing or plan comparisons that build up rather than sitting side by side
- Case-study chapters on a portfolio page, one pinned section per project
- Onboarding or how-it-works steps that stay visible as the next one arrives

## How It Works

One ScrollTrigger pins the frame and scrubs a single GSAP timeline that spends one unit of timeline time per arriving card. The arriving panel gets a fromTo from below the frame to a few pixels past its resting line (power2.out), followed by a short relax onto the line; the cards beneath get their depth values as explicit from/to pairs with a stagger, so a mid-scroll resize cannot re-record the deck's start state.

Every distance is a function value with invalidateOnRefresh, so the off-frame start position and the pin length are re-measured rather than cached. Depth is clamped so cards deeper than the cap freeze in place and the deck cannot outgrow a short viewport.

Scroll velocity from getVelocity() is clamped, mapped to degrees and pushed through gsap.quickTo with a back.out ease, driving rotationX, a y drag and a scaleY squash on the deck from one value; a ticker watchdog releases it back to zero once scrolling stops. The counter and rail read the timeline's own onUpdate rather than the scroll handler, so they track the scrubbed visual state instead of the raw wheel.

## Integration Preview

How this effect integrates into a page. The full documentation (examples, events, programmatic API, customization guide) ships with the download.

### Quick Start

**1. Add to your HTML `<head>`:**

```html
<link rel="stylesheet" href="path/to/style.css">
```

**2. Add before the closing `</body>` tag:**

```html
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/ScrollTrigger.min.js"></script>
<!-- Optional: the scrub reads noticeably smoother with Lenis -->
<script src="https://unpkg.com/lenis@1.3.17/dist/lenis.min.js"></script>
<script src="path/to/script.js"></script>
```

**3. Add the effect HTML anywhere in your `<body>`:**

```html
<section class="stack" data-stack aria-label="Scroll-stacking card deck">
  <div class="stack__viewport" data-stack-viewport>
    <p class="focused-cue">
      <span class="focused-cue__dot" aria-hidden="true"></span>
      <span>Scroll to build the deck</span>
      <span class="focused-cue__divider focused-cue__velocity" aria-hidden="true">/</span>
      <span class="focused-cue__velocity">Flick for velocity</span>
    </p>

    <div class="stack__deck" data-stack-deck>
      <article class="stack__card" data-stack-card>
        <span class="stack__index" aria-hidden="true">01</span>
        <span class="stack__eyebrow">Deck / 01</span>
        <div class="stack__body">
          <h3 class="stack__title">The pile builds itself</h3>
          <p class="stack__copy">Each panel rides up from below the frame and locks.</p>
        </div>
        <p class="stack__meta">Arrives &middot; Locks &middot; Stays</p>
      </article>

      <!-- more .stack__card panels, in the order they should arrive -->
    </div>

    <!-- Optional chip. Keep it AFTER the deck: it must be the last child of the
         frame so nothing can paint over it. -->
    <div class="stack__hud">
      <span class="stack__hud-label">Locked</span>
      <span class="stack__count" data-stack-count>05 / 05</span>
      <span class="stack__rail" aria-hidden="true"><span class="stack__rail-fill" data-stack-rail></span></span>
    </div>

  </div>
</section>
```

Two or more `[data-stack-card]` panels are required; below that the script leaves the markup alone. Any number above that works, the scroll length grows with the card count and every distance is re-measured on refresh.

**4. Set the counter's total to your card count.** `data-stack-count` starts as plain text (`05 / 05`) so the no-JS and reduced-motion document reads correctly. The script overwrites it once it takes over.

### Options

All set on the `[data-stack]` section:

| Attribute | Values | Default | Description |
|-----------|--------|---------|-------------|
| `data-offset` | Any number (px) | `26` | Visible edge of each buried card per level of depth. `0` removes the peek and the deck reads as one card being replaced |
| `data-scale` | `0` to `~0.09` | `0.055` | Scale removed per level of depth. Past ~0.09 the third card down is small enough to read as a different component |
| `data-dim` | `0` to `1` | `0.16` | Opacity of black laid over each level of depth, capped at `0.88` in total |
| `data-max-depth` | Whole number ≥ 1 | `4` (`3` on touch) | How many levels keep moving. Deeper cards freeze at this depth, so the deck cannot grow past the top of a short viewport |
| `data-lean` | `true`, `false` | `true` | The velocity lean. Always off on coarse pointers regardless of this value |
| `data-scrub` | Any number | `1` | Multiplier on the scroll distance each card takes to arrive. `1.5` is a slower, more deliberate deal; `0.6` is brisk |
| `data-stack-bar` | Any CSS selector | none | Selector for a fixed page bar. Its height is measured live, the pin starts below it and the frame shrinks to the space left over |

On coarse pointers `data-offset` and `data-scale` are scaled down automatically (to 55% and 70%) and the depth cap drops, so a five-card deck still reads as a pile at 375px without overflowing a short viewport.

### Accessibility

- **Reduced motion**: `prefers-reduced-motion: reduce` skips the pin entirely. The panels stay the plain, fully visible vertical column the markup already is, and the counter shows the final value. Nothing is animated, pinned or hidden.
- **Touch devices**: the mechanic is scroll-driven, so it works as-is. Depth offsets shrink, the visible depth is capped and the velocity lean is dropped, touch inertia makes the velocity reading noisy enough that the lurch reads as a bug rather than a flourish.
- **No JavaScript**: the base stylesheet is the readable column. The only thing gated behind the script's `.stack--live` class is the pinned layout, and the velocity half of the cue stays hidden because it is not true without the script.
- **Screen readers**: cards are `<article>` elements in reading order with real headings; the index watermark, progress rail and dim overlays are `aria-hidden`.

### Dependencies

**Required:**
- GSAP 3.12+
- ScrollTrigger plugin

**Optional:**
- Lenis (smooth scroll). The scrubbed deck reads noticeably smoother with it; the script wires `lenis.on('scroll', ScrollTrigger.update)` automatically when Lenis is present and works on native scroll when it is not.

## What You Get

- `index.html`: working demo page
- `assets/script.js`: commented, readable source
- `assets/style.css`: effect styles
- `README.md`: full documentation with examples and framework integration notes
- `LICENSE.txt`: standard license terms
- Lifetime updates: re-download anytime from your library

## Get the Code

This is a premium effect. The standard license costs £5 one-time and covers unlimited personal and commercial projects with no attribution required. The only restrictions: no redistribution of the code itself and no competing effect libraries.

- [Buy Scroll Stack Cards](https://gsapvault.com/effects/scroll-stack-cards)
- [Effects & Templates Vault (£39 one-time, best value): every current and future effect and template](https://gsapvault.com/effects)

## Judge the Code Quality First

These related effects are free with complete source published, written to the same production standard (cleanup functions, reduced-motion support, framework-agnostic):

- [Scroll Progress Indicator](https://gsapvault.com/effects/scroll-progress.md): Four scroll progress visualization styles using GSAP ScrollTrigger. Includes animated bar, circle, rail track, and percentage counter indicators.
- [CSS Scroll Reveal](https://gsapvault.com/effects/css-scroll-reveal.md): Pure CSS scroll-triggered entrance animations. No JavaScript required. Uses native CSS scroll-driven animations for fade, slide, and scale effects.
- [Parallax Hero](https://gsapvault.com/effects/parallax-hero.md): A layered hero section where background blobs, headline, and foreground labels scroll at different speeds, creating scrubbed parallax depth.

---

From [GSAP Vault](https://gsapvault.com): production-ready GSAP animation effects. Full catalog for agents: https://gsapvault.com/llms-full.txt
