document.addEventListener("DOMContentLoaded", function () { gsap.registerPlugin(ScrollTrigger); const cards = document.querySelectorAll(".stacking-cards .card"); const cardWidth = cards[0].offsetWidth; const gap = 20; const totalOffset = (cardWidth + gap) * (cards.length - 1); const scrollTrigger = ScrollTrigger.create({ trigger: ".stacking-cards", pin: true, start: "top top", end: "+=" + totalOffset + "px", scrub: true, markers: true, onUpdate: (self) => { const progress = self.progress; // Global progress [0 - 1] cards.forEach((card, index) => { const cardStart = index / cards.length; // Start point for this card const cardEnd = (index + 1) / cards.length; // End point for this card const cardProgress = (progress - cardStart) / (cardEnd - cardStart); // Normalize progress for this card if (progress >= cardStart && progress < cardEnd) { // Active card during its scroll range gsap.to(card, { x: -(index * (cardWidth + gap)), // Move to active position duration: 0.3, ease: "power1.out", }); card.classList.add("active"); card.classList.remove("stacked"); card.classList.remove("vertical-text"); } else if (progress >= cardEnd) { // Cards before the active one gsap.set(card, { x: -(index * (cardWidth + gap)), // Stack position }); card.classList.add("stacked"); card.classList.remove("active"); } else { // Cards after the active one gsap.set(card, { x: 0, // Keep in starting position until their turn }); card.classList.remove("active", "stacked", "vertical-text"); } }); }, onLeave: () => { // At the end of the scroll, remove the 'stacked' class from all cards cards.forEach((card) => { card.classList.remove("stacked"); }); }, }); });
Preview:
downloadDownload PNG
downloadDownload JPEG
downloadDownload SVG
Tip: You can change the style, width & colours of the snippet with the inspect tool before clicking Download!
Click to optimize width for Twitter