webdevpuneet.com

Scroll Card Fan — Free GSAP Deck Spread Snippet

Scroll Card Fan · Scroll · Plain HTML, CSS & JS · Live preview

Copy & paste

Source Code

<section class="scf-top"><p>Scroll ↓</p></section>
<section class="scf-stage" id="scfStage">
  <div class="scf-heading">
    <h2>Pick a card</h2>
    <p>The deck fans open as you scroll.</p>
  </div>
  <div class="scf-deck" id="scfDeck">
    <div class="scf-card" style="--cc1:#6366f1;--cc2:#4338ca"><span>⚡</span><em>Speed</em></div>
    <div class="scf-card" style="--cc1:#a855f7;--cc2:#7e22ce"><span>🛡️</span><em>Security</em></div>
    <div class="scf-card" style="--cc1:#06b6d4;--cc2:#0e7490"><span>📈</span><em>Scale</em></div>
    <div class="scf-card" style="--cc1:#10b981;--cc2:#047857"><span>🧩</span><em>Plugins</em></div>
    <div class="scf-card" style="--cc1:#f59e0b;--cc2:#b45309"><span>🤝</span><em>Support</em></div>
  </div>
</section>
<section class="scf-bottom"><p>Scroll up to sweep the fan back into a deck.</p></section>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Low-pivot arc
transform-origin below the card creates the fan.
Center-distance spread
Rotation scales with each card's offset from mid.
Messy-pile start
Resting offsets make the deck feel physical.
One-gesture open
All cards tween from position 0 together.
Focal accent
Center card lifts late in the timeline.
Pinned + scrubbed
Fan progress tracks the scrollbar exactly.
Transform-only
Rotation and translation, zero layout cost.
Count-agnostic
Works with any number of cards unchanged.

About this UI Snippet

Scroll Card Fan — Fan a Deck of Cards Open With the Scrollbar

Screenshot of the Scroll Card Fan snippet rendered live

The scroll card fan takes a neat pile of cards and spreads it into a hand-of-cards arc as you scroll — the same gesture as fanning playing cards on a table, driven by the scrollbar and fully reversible. It's a compact way to introduce a set of features, plans, or categories with one satisfying motion. This snippet builds it with GSAP ScrollTrigger (from a CDN) and a single transform-origin trick.

One low pivot point creates the entire arc

Every card is absolutely stacked in the same box with transform-origin: 50% 130% — a pivot *below* the card's bottom edge. Rotating around that shared low point automatically swings each card outward and upward along a circular arc, exactly like cards pinched at one corner. Without the shifted origin you'd have to hand-calculate an x/y/rotation triple per card to fake the arc; with it, rotation alone does 90% of the work.

Spread is computed from distance-to-center

Each card's target rotation is (i − mid) × 16°, where mid is the middle index. The center card stays upright, neighbors lean ±16°, outer cards ±32° — a symmetric fan that works for any odd or even card count without editing the tween code. A small x: spread × 24 widens the fan beyond what pure rotation gives, and y: |spread| × −6 lifts outer cards slightly so their top corners align along the arc.

The deck starts as a believable pile

Before the trigger runs, gsap.set gives each card a resting offset — 1.5° of alternating rotation and 2px of vertical stagger. That messy-pile look matters: fanning from a mathematically perfect stack reads as sterile, while fanning from a slightly uneven pile reads as physical. Because these offsets are set as start values, the scrubbed tween interpolates from pile to fan and back without snapping.

All cards animate at position 0, plus one accent later

Every card's tween is placed at timeline position 0 so the whole fan opens as one gesture rather than card-by-card. Then, at 75% of the timeline, the center card lifts 34px and scales to 1.06 — a focal accent that lands only after the fan is mostly open, giving the sequence a beginning (spread) and an end (highlight) within one scroll.

Pinned, scrubbed, and compositor-only

The stage pins for +=160% with scrub: 0.4, so the fan opens exactly as far as you've scrolled and glides slightly after each wheel tick. Rotation, translation, and scale are all transforms — no layout or paint work — and will-change: transform promotes each card to its own layer up front, avoiding promotion jank mid-scroll.

z-order comes free from source order

Later cards in the DOM paint on top, so the rightmost card naturally overlaps leftward like a real right-handed fan. Reverse the DOM order (or set explicit z-indexes) to flip the overlap direction.

The fanned cards stay fully interactive

Nothing about pinning or scrubbing disables the DOM: the cards remain real elements at every scrub position, so links, hover states, and click handlers work mid-fan or fully open. ScrollTrigger's pin spacer wraps the stage without intercepting pointer events, and because the cards only carry transforms, their hit areas travel exactly with their rendered positions. A common production touch is enabling a lift-on-hover only once the fan is open — gate it by checking tl.progress() > 0.9 in a pointerenter handler, or toggle a class from an onComplete-style position callback on the timeline.

Customizing it

Change the spread angle, card count (the mid math adapts), or make each card a link. Pair the fan with a scroll sticky stack for the section after, a scroll 3d cards alternative entrance, or stacked cards for a hover-driven cousin.

Step by step

How to Use

  1. 1
    Add the GSAP CDNsInclude gsap and ScrollTrigger from the CDN panel.
  2. 2
    Paste HTML, CSS, and JSFive cards stack into a slightly messy pile.
  3. 3
    Scroll into the stageIt pins and the deck begins fanning open.
  4. 4
    Finish the scrollThe center card lifts and scales as the focal point.
  5. 5
    Scroll back upThe fan sweeps back into the pile — fully scrubbed.
  6. 6
    Add or restyle cardsThe distance-to-center math adapts to any count.

Real-world uses

Common Use Cases

Feature introductions
Fan five value propositions open from a pile, then detail them with scroll sticky features below.
Plan pickers
Fan pricing tiers open before a pricing card section, with rotation scaled by each card's distance from the middle.
Portfolio decks
Spread project cards as a portfolio deck and let a scroll gallery pin take over to browse each one in detail.
Game and NFT sites
Create literal card reveals, adding a 3D flip card to turn each card over once the hand is open.
Onboarding choices and transitions
Fan the paths a user can take, echoing stacked cards, or open the fan and hand off to a scroll sticky stack.
Related: Scroll Chapter Sidebar Story
See the Scroll Chapter Sidebar Story for a related scroll pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

Every card shares transform-origin: 50% 130% — a pivot below its bottom edge. Rotating around that low point swings the card outward and upward along a circle, like playing cards pinched at one corner. Without the shifted origin you would need hand-tuned x, y, and rotation per card to fake the same arc.

From its distance to the center index: rotation is (i − mid) × 16°, so the middle card stays upright and outer cards lean the most, symmetrically. A small x shift widens the fan and a negative y proportional to |spread| aligns the top corners along the arc. Change one multiplier to open or tighten the whole fan.

Yes — add or remove .scf-card elements and everything adapts, because mid is computed as (count − 1) / 2 and every tween derives from distance-to-center. For big decks, lower the 16° multiplier (for example 60° total divided by count) so outer cards don't rotate past legibility, and consider raising the pinned end distance.

gsap.set gives each card 1.5° of alternating tilt and a 2px vertical stagger before the trigger runs. A mathematically perfect stack fans open looking sterile; a slightly uneven pile reads as a physical deck. Since these are start values, the scrubbed tween interpolates smoothly from pile to fan and back without any snap.

Fully — pinning and scrubbing never disable the DOM, and transform-only animation means each card's hit area travels with its rendered position. Wrap card contents in anchors or attach click handlers as usual. To add hover lifts only after the fan opens, gate the effect on tl.progress() > 0.9 so a half-open deck doesn't respond like a finished layout.

Render cards from an array, then build the set() offsets and timeline in a mount effect (useEffect, onMounted, or ngAfterViewInit) inside gsap.context scoped to a container ref, reverting it in the cleanup so the pin unregisters on unmount. Compute mid from the array length so adding data adds cards. Card visuals translate directly to Tailwind gradient and shadow utilities.