Sticky Stacked Testimonial Cards on Scroll
Cards pin with CSS position: sticky and each one scales back as the next slides over it, so the stack is native scroll plus a scrubbed transform: no pinning spacers, no layout shift.
Works in Webflow Framer React Zero CLS
Customize before you copy
Showing the Webflow version
You are a senior creative developer. Build a "sticky stacked cards" testimonial section.
Behaviour:
- 4–6 testimonial cards in a vertical list. Each card sticks to the viewport with CSS position: sticky; card i sticks at top: calc(12vh + i * 24px), so earlier cards peek out above later ones like a deck.
- As card i+1 scrolls up over card i, scrub card i down to scale 0.92 and opacity 0.6 (transform-origin: top center). The last card never shrinks.
- The scrub runs from the moment the NEXT card enters the viewport until it reaches its own sticky offset, with 0.8s of smoothing.
Hard constraints:
- Animate transform and opacity only. Never animate top, height, margin or filter.
- Do NOT pin with JavaScript: sticky positioning does the pinning, so there are no pin-spacers and zero CLS. No ancestor of the cards may have overflow: hidden (it silently breaks sticky).
- Reduced-motion users get the plain sticky stack with no scrub.
- Add will-change: transform only while a card is animating, never permanently.
- Semantic markup: a <section> with an aria-labelledby heading, each card a <figure> with <blockquote> and <figcaption>.
Deliver the structure, the CSS and the script with a cleanup, then tell me which three values to tweak for a tighter or looser deck.
Target platform: Webflow.
- Build the structure as native Webflow Designer elements with Client-First class names (section_[name], [name]_component, [name]_item). No div soup from an HTML embed unless I ask for one.
- Hook every behaviour to custom attributes (data-gsap="…"), never to class names, so designers can restyle without breaking the script.
- Load GSAP through Webflow's built-in GSAP integration (Site settings) or the jsDelivr CDN tags, and put the init script in Page settings → Before </body> tag, wrapped in Webflow.push(() => { … }).
- Say which parts go in Designer styles, which in a page-head <style>, and which in the body script.
- Designer structure: section_testimonials > testimonials_list (data-gsap="stack-list") > testimonials_card (data-gsap="stack-card") ×4–6. The script sets each card's --i, so editors can add or remove cards freely (a CMS list works too).
Motion timing: "Editorial". Use a base duration of 0.8s, ease "expo.out" (CSS: cubic-bezier(0.16, 1, 0.3, 1)), stagger 0.08s, scrub smoothing 0.8, and springs of stiffness 220 / damping 22, everywhere a duration, ease or spring appears.Loading the snippet…