JSFiddle - React, Tailwind, and code Playground

by Jordan Sayner

HTML

<div class="card-wrapper">
  <div class="card" data-scroll-offset="10">
    <h2>Card Title</h2>
    <p>Card description...</p>
  </div>
  <div class="card" data-scroll-offset="10">
    <h2>Another title</h2>
    <p>More information...</p>
  </div>
  <div class="card" data-scroll-offset="10">
    <h2>Another title</h2>
    <p>More information...</p>
  </div>
  <div class="card" data-scroll-offset="10">
    <h2>Another title</h2>
    <p>More information...</p>
  </div>
  <div class="card" data-scroll-offset="10">
    <h2>Another title</h2>
    <p>More information...</p>
  </div>
  <div class="card" data-scroll-offset="10">
    <h2>Another title</h2>
    <p>More information...</p>
  </div>
  <div class="card" data-scroll-offset="10">
    <h2>Another title</h2>
    <p>More information...</p>
  </div>
  <div class="card" data-scroll-offset="10">
    <h2>Another title</h2>
    <p>More information...</p>
  </div>
  <div class="card" data-scroll-offset="10">
    <h2>Another title</h2>
    <p>More information...</p>
  </div>
  <div class="card" data-scroll-offset="10">
    <h2>Another title</h2>
    <p>More information...</p>
  </div>
</div>

CSS

.card-wrapper {
  display: flex;
  flex-direction: column;
  margin: 0 auto;
  max-width: 700px; /* Adjust as needed */
}

.card {
  background-color: #fff;
  border-radius: 5px;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
  padding: 20px;
  margin-bottom: 20px; /* Adjust spacing between cards */
  position: sticky;
  top: var(--card-offset, 0);
  transition: transform 0.2s ease-in-out, filter 0.2s ease-in-out;
                    aspect-ratio: 1220 / 766;
}

.card:first-child {
  margin-top: 0; /* Remove top margin for first card */
}

.card--small {
  transform: scale(0.8);
  filter: blur(4px);
}

JavaScript

const cards = document.querySelectorAll('.card');

window.addEventListener('scroll', () => {
  cards.forEach(card => {
    const cardTop = card.getBoundingClientRect().top;
    const cardHeight = card.offsetHeight;
    const viewportHeight = window.innerHeight;

    const isFullyScrolled = cardTop + cardHeight <= viewportHeight;

    if (isFullyScrolled) {
      card.style.transform = 'scale(0.8)';
      card.style.filter = 'blur(4px)';
    } else {
      const scrollOffset = card.dataset.scrollOffset;
      const scrollProgress = Math.max(0, Math.min(1, (scrollOffset - cardTop) / scrollOffset));
      const scale = 1 - scrollProgress * 0.2;
      const blur = scrollProgress * 4;
      card.style.transform = `scale(${Math.max(0.8, scale)})`;
      card.style.filter = `blur(${Math.min(4, blur)}px)`;
    }
  });
});