Stacking Cards Parallax

Full-viewport cards that stack on top of each other as you scroll, with the previous card sliding down and its inner image rotating and translating for a parallax depth effect. Built with GSAP ScrollTrigger scrubbed to scroll position.

gsapscrolltriggerstackingcardsparallaxscroll

Setup — External Scripts

Setup: External Scripts
html
<script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/ScrollTrigger.min.js"></script>

Code

index.html
html
<div class="stacking-cards__collection">
  <div class="stacking-cards__list">
    <div data-stacking-cards-item="" class="stacking-cards__item is--green">
      <h1 class="stacking-cards__item-h"><span class="stacking-card__heading-faded">emerald</span><br>new beginnings</h1>
      <img src="https://cdn.prod.website-files.com/68a581f419b751517e3d40c0/68a58a5f107533572d9566ce_Glass%20and%20Marble%20Cube%20Composition.avif" data-stacking-cards-img="" class="stacking-cards__item-img">
      <div class="stacking-cards__item-top">
        <span class="stacking-card__top-span">Gemstones</span>
        <span class="stacking-card__top-span">01 / 05</span>
      </div>
    </div>
    <div data-stacking-cards-item="" class="stacking-cards__item is--purple">
      <h1 class="stacking-cards__item-h"><span class="stacking-card__heading-faded">amethyst</span><br>supports good health</h1>
      <img src="https://cdn.prod.website-files.com/68a581f419b751517e3d40c0/68a58a5ff31697ad7106fc1d_Minimalist%20Stacked%20Cubes%20(1).avif" data-stacking-cards-img="" class="stacking-cards__item-img">
      <div class="stacking-cards__item-top">
        <span class="stacking-card__top-span">Gemstones</span>
        <span class="stacking-card__top-span">02 / 05</span>
      </div>
    </div>
    <div data-stacking-cards-item="" class="stacking-cards__item is--blue">
      <h1 class="stacking-cards__item-h"><span class="stacking-card__heading-faded">sapphire</span><br>wisdom &amp; learning</h1>
      <img src="https://cdn.prod.website-files.com/68a581f419b751517e3d40c0/68a58a5fe3e373d4afe5f860_Stacked%20Cubes%20on%20Marble.avif" data-stacking-cards-img="" alt="" class="stacking-cards__item-img">
      <div class="stacking-cards__item-top">
        <span class="stacking-card__top-span">Gemstones</span>
        <span class="stacking-card__top-span">03 / 05</span>
      </div>
    </div>
    <div data-stacking-cards-item="" class="stacking-cards__item is--brown">
      <h1 class="stacking-cards__item-h"><span class="stacking-card__heading-faded">topaz</span><br>re-motivating</h1>
      <img src="https://cdn.prod.website-files.com/68a581f419b751517e3d40c0/68a58a5f732de55938114f5f_Minimalist%20Stacked%20Cubes.avif" data-stacking-cards-img="" class="stacking-cards__item-img">
      <div class="stacking-cards__item-top">
        <span class="stacking-card__top-span">Gemstones</span>
        <span class="stacking-card__top-span">04 / 05</span>
      </div>
    </div>
    <div data-stacking-cards-item="" class="stacking-cards__item is--red">
      <h1 class="stacking-cards__item-h"><span class="stacking-card__heading-faded">rose quartz</span><br>peace and love</h1>
      <img src="https://cdn.prod.website-files.com/68a581f419b751517e3d40c0/68a58a5ff1fd91f3ae23c706_Geometric%20Blocks%20Arrangement.avif" data-stacking-cards-img="" class="stacking-cards__item-img">
      <div class="stacking-cards__item-top">
        <span class="stacking-card__top-span">Gemstones</span>
        <span class="stacking-card__top-span">05 / 05</span>
      </div>
    </div>
  </div>
</div>
styles.css
css
.stacking-cards__collection {
  width: 100%;
}

.stacking-cards__list {
  flex-flow: column;
  display: flex;
}

.stacking-cards__item {
  color: #fff;
  border-top-left-radius: 1em;
  border-top-right-radius: 1em;
  flex-flow: column;
  justify-content: center;
  align-items: center;
  width: 100%;
  min-height: 100vh;
  margin-top: -1em;
  padding-top: 5em;
  padding-bottom: 5em;
  display: flex;
  position: relative;
}

.stacking-cards__item.is--green { background-color: #1a261e; }
.stacking-cards__item.is--purple { background-color: #544a58; }
.stacking-cards__item.is--blue { background-color: #1a232e; }
.stacking-cards__item.is--brown { background-color: #62594c; }
.stacking-cards__item.is--red { background-color: #1f1715; }

.stacking-cards__item-h {
  text-align: center;
  text-transform: uppercase;
  margin-top: 0;
  margin-bottom: 0;
  font-size: 10vw;
  font-weight: 700;
  line-height: .8;
}

.stacking-card__heading-faded {
  opacity: .5;
}

.stacking-cards__item-img {
  aspect-ratio: 1;
  border-radius: .75em;
  width: 30vw;
  margin-top: -1vw;
}

.stacking-cards__item-top {
  justify-content: space-between;
  align-items: center;
  display: flex;
  position: absolute;
  top: 2.5em;
  left: 2.5em;
  right: 2.5em;
}

.stacking-card__top-span {
  font-size: 1em;
  font-weight: 500;
}
script.js
javascript
gsap.registerPlugin(ScrollTrigger);

function initStackingCardsParallax() {
  const cards = document.querySelectorAll("[data-stacking-cards-item]");

  if (cards.length < 2) return;

  cards.forEach((card, i) => {
    if (i === 0) return;

    const previousCard = cards[i - 1];
    if (!previousCard) return;

    const previousCardImage = previousCard.querySelector("[data-stacking-cards-img]");

    let tl = gsap.timeline({
      defaults: {
        ease: "none",
        duration: 1
      },
      scrollTrigger: {
        trigger: card,
        start: "top bottom",
        end: "top top",
        scrub: true,
        invalidateOnRefresh: true
      }
    });

    tl.fromTo(previousCard, { yPercent: 0 }, { yPercent: 50 })
      .fromTo(previousCardImage, { rotate: 0, yPercent: 0 }, { rotate: -5, yPercent: -25 }, "<");
  });
}

document.addEventListener("DOMContentLoaded", () => {
  initStackingCardsParallax();
});

Guide

Important

Do not use display: flex; on the parent of .stacking-cards__collection — this will break the effect. For most users this is the <body> or <main> element.

Cards

The only elements required are the cards themselves. Give each card the [data-stacking-cards-item] attribute. The script skips the first card and uses each subsequent card as a scroll trigger to animate the previous one.

Parallax Distance

To change how dramatic the parallax effect is, adjust the yPercent value in the timeline. The current setup moves the previous card down by 50% of its own height.

Animating Card Content

Tag any inner element with a [data-stacking-cards-X] attribute (e.g. [data-stacking-cards-heading]) to animate it. Query it as a variable inside the forEach loop and add a fromTo tween to the existing timeline.

Ideas

Fade out the previous card while parallax is happening, fade only an inner container while the card stays, or scale the card down simultaneously with the yPercent movement.