Scroll Polaroid Stack Flip — Free HTML CSS JS Snippet

Scroll Polaroid Stack Flip · Scroll · Plain HTML, CSS & JS · Live preview

What's included

Features

Randomized per-card rotation via CSS custom properties for an authentic messy pile
Real grid target positions measured at runtime with getBoundingClientRect, not hardcoded
Two-part peel-then-settle motion using 3D rotateX and perspective for a lifted feel
Sequential fractional-timeline segments stagger each card's turn
Ascending z-index keeps stack order visually correct before cards peel away
Fully reversible — cards lift back out of the grid and return to the messy pile on scroll-up
Warm cream/kraft-paper color palette with soft box-shadow depth
Responsive stack and grid sizing at the 640px breakpoint

About this UI Snippet

Scroll Polaroid Stack Flip — getBoundingClientRect Targeting & 3D Peel-and-Settle Motion

Screenshot of the Scroll Polaroid Stack Flip snippet rendered live

This snippet starts with a messy pile of randomly-rotated polaroid photo cards and, as the user scrolls, peels them off one at a time with a subtle 3D lift before flying each one into its own cell of a neat underlying grid — a satisfying "getting organized" transformation.

Randomized resting rotation via CSS custom properties

Each .polaroid sets its own --mr (mess rotation) custom property consumed by transform: rotate(var(--mr)), giving the initial pile a natural, hand-tossed look where no two photos sit at the same angle — all defined inline in the HTML so the messy arrangement is easy to art-direct per photo.

Measuring real target positions with getBoundingClientRect

Rather than hardcoding each photo's final grid position, a hidden .grid-slots CSS Grid layout defines the actual target cells, and getSlotOffset() measures the pixel offset from the stack's origin to each slot using getBoundingClientRect() at runtime — so the grid can be reflowed (different columns, gaps, or a responsive breakpoint) without ever touching the animation's position math.

Two-part motion per card: peel, then settle

Each polaroid's segment is split into two tweens: first a small lift-and-tilt (rotateX: -40, scale: 1.05, a negative y and boosted zIndex) simulating a card being physically picked up off the pile with a 3D peel using transform-style: preserve-3d and the stage's perspective, then a second tween flying it to its measured grid offset while resetting rotation and scale to flat and neutral — reading as the photo being placed down into its slot.

Sequential ordering via fractional timeline segments

The familiar segStart = i / n pattern (also used in Scroll Book Shelf Slide and Scroll Terrain Contour Lines) gives each polaroid its own slice of the scroll range so they peel and settle one after another rather than all at once, with ascending zIndex ensuring later cards in the original stack visually sit on top while still in the pile.

Fully reversible

Every tween lives on one scrub: true timeline, so scrolling back up lifts each photo back out of its grid slot, tilts it back in 3D, and drops it back into its messy pile position and rotation — undoing the sort in reverse order.

Pair this with Scroll Card Deck Shuffle for a related stack-transformation technique.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Paste this snippet's HTML, CSS and JS into an AI coding assistant and ask it to explain why getSlotOffset() measures grid positions with getBoundingClientRect at runtime instead of hardcoding each polaroid's final x/y — the answer is about keeping the animation correct across any grid layout or screen size without touching the JS. It's a good one to extend with an assistant's help: ask it to make the grid responsive (fewer columns on mobile) while keeping the animation targeting correct, to add a subtle random landing-rotation to each photo once it settles into its slot for extra realism, or to add a real 3D flip (rotateY 180 degrees) revealing a caption on the back of each polaroid as it settles.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a scroll-driven "polaroid stack sorts into a grid" animation in HTML, CSS and JavaScript using GSAP and ScrollTrigger — no canvas, no WebGL.

Requirements:
- Render a small messy pile of absolutely-positioned "polaroid" photo card divs, all stacked at the same origin point, each given its own randomized rotation via an inline CSS custom property so the pile looks naturally hand-tossed, plus ascending z-index matching their stacking order.
- Separately define a hidden CSS Grid of "slot" placeholder elements representing the neat final target layout (e.g. 3 columns by 2 rows).
- Write a helper function that measures each slot's actual rendered position relative to the stack's origin using getBoundingClientRect, so the animation always targets real layout positions rather than hardcoded coordinates.
- Wrap the stack in a tall scroll section and, using one GSAP timeline attached via ScrollTrigger with scrub: true, give each polaroid its own fractional slice of the scroll range containing two sequential tweens: first a small 3D "peel" lift (negative rotateX with a perspective set on the parent, a slight scale up, a small y lift, and a boosted z-index) simulating the card being picked up off the pile, then a second tween flying it to its measured grid slot position while resetting rotation, rotateX and scale back to neutral, so it reads as the photo settling flat into its slot.
- The whole sequence must animate forward and reverse cleanly as the user scrolls down and back up, lifting cards back out of the grid and returning them to their messy pile positions and rotations.
- Use a warm cream and kraft-paper color palette with soft drop shadows for depth.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Source Code

<div class="hint">Scroll ↓ to sort the photo stack</div>
<div class="polaroid-wrap">
  <div class="polaroid-stage">
    <div class="grid-slots" id="gridSlots">
      <div class="slot" style="--gx:0;--gy:0"></div>
      <div class="slot" style="--gx:1;--gy:0"></div>
      <div class="slot" style="--gx:2;--gy:0"></div>
      <div class="slot" style="--gx:0;--gy:1"></div>
      <div class="slot" style="--gx:1;--gy:1"></div>
      <div class="slot" style="--gx:2;--gy:1"></div>
    </div>
    <div class="stack" id="stack">
      <div class="polaroid" style="--hue:200;--mr:-8deg" data-photo="1"><div class="photo"></div><div class="caption">Beach Day</div></div>
      <div class="polaroid" style="--hue:20;--mr:6deg" data-photo="2"><div class="photo"></div><div class="caption">Road Trip</div></div>
      <div class="polaroid" style="--hue:280;--mr:-4deg" data-photo="3"><div class="photo"></div><div class="caption">Sunset</div></div>
      <div class="polaroid" style="--hue:100;--mr:9deg" data-photo="4"><div class="photo"></div><div class="caption">Friends</div></div>
      <div class="polaroid" style="--hue:340;--mr:-11deg" data-photo="5"><div class="photo"></div><div class="caption">Camping</div></div>
      <div class="polaroid" style="--hue:50;--mr:5deg" data-photo="6"><div class="photo"></div><div class="caption">City Lights</div></div>
    </div>
  </div>
</div>

Step by step

How to Use

  1. 1
    Scroll through the previewScroll down slowly — each polaroid lifts off the messy pile with a 3D tilt, then flies into its own slot in the neat grid below.
  2. 2
    Add or remove photosAdd a .polaroid element to #stack with its own --hue and --mr custom properties, and a matching .slot element to #gridSlots — the JS reads both arrays' lengths automatically.
  3. 3
    Use real photosReplace the .photo div's gradient background with an <img> or background-image pointing at your own image.
  4. 4
    Change the grid layoutEdit grid-template-columns/rows on .grid-slots — getSlotOffset() measures actual rendered slot positions, so any grid layout works without touching the JS.
  5. 5
    Adjust the peel height and tiltChange the rotateX, scale and y values in the first tween of each card's segment to make the peel more or less dramatic.
  6. 6
    Export in your formatClick "HTML" for a standalone file, "JSX" for a React component, or "Tailwind" for a React + Tailwind version.

Real-world uses

Common Use Cases

Photo gallery or portfolio organization reveal
A literal "sorting the mess into order" animation for a photography portfolio or gallery landing page.
Memory or social app onboarding
Use the peel-and-sort motion to represent photos being organized into albums in a memories or social app.
Scrapbook or travel-journal storytelling
A nostalgic, hand-placed aesthetic well suited to travel journals, scrapbook sites, or personal blogs.
Team or testimonial grid reveal
Repurpose each polaroid as a team member photo or testimonial card that organizes into a neat grid.
Learn runtime-measured animation targets
Study how getBoundingClientRect lets an animation target a real, reflow-aware layout instead of hardcoded coordinates.
Learn 3D peel/lift transforms
See how perspective plus rotateX and preserve-3d combine to simulate an object being physically lifted off a surface.

Got questions?

Frequently Asked Questions

A hidden CSS Grid of .slot elements defines the real target layout. getSlotOffset() measures each slot's position relative to the stack's origin with getBoundingClientRect() at runtime, so the animation always targets the grid's actual rendered position rather than a hardcoded coordinate.

The stage has a CSS perspective set, each card has transform-style: preserve-3d, and the first tween in each card's segment applies a negative rotateX along with a slight scale-up and lift — combined, this reads as the card tilting up and off the pile in 3D before flying to its slot.

Yes — edit grid-template-columns/rows on .grid-slots and add matching .slot elements; since positions are measured live with getBoundingClientRect, no animation code needs to change.

Replace each .photo div's CSS gradient background with a background-image or swap it for an <img> tag sized to fill the same area — the surrounding polaroid frame and caption styling stay the same.

Call ScrollTrigger.refresh() after a resize (or listen for a resize event) so getSlotOffset() re-measures the slots' new positions; the snippet already calls refresh() once on load to establish correct initial positions.