[해피CGI][cgimall] 스택형 카드 스크롤 애니메이션 (Stacking card)
세로 스크롤에 따라 카드들이 순차적으로 고정되며 확대되는 스택 효과. GSAP ScrollTrigger를 활용한 인터랙티브 디자인 예제입니다.
이 예제는 사용자가 페이지를 스크롤할 때 카드들이 하나씩 고정되면서 부드럽게 확대되는 스택(Stacking) 애니메이션 효과를 구현합니다. 각 카드가 화면 중앙에 도달하면 잠시 고정되었다가 다음 카드가 이어지는 방식으로 자연스러운 흐름을 보여주며, GSAP의 ScrollTrigger와 ScrollSmoother 플러그인을 활용하여 스크롤 위치와 애니메이션이 매끄럽게 동기화됩니다.

HTML 구조
<main>
<h1>Stacking Card</h1>
<div class="stacking">
<div class="stacking__card">
<h2>Title</h2>
<div class="stacking__content">
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Consequatur error, reiciendis enim nulla voluptate non aspernatur rem dicta alias perspiciatis qui nobis ratione similique incidunt magnam ut commodi quae eaque!</p>
</div>
</div>
<div class="stacking__card" style="background-color: #d6e684;">
<h2>Title</h2>
<div class="stacking__content">
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Consequatur error, reiciendis enim nulla voluptate non aspernatur rem dicta alias perspiciatis qui nobis ratione similique incidunt magnam ut commodi quae eaque!</p>
</div>
</div>
<div class="stacking__card" style="background-color: #4fc1ed;">
<h2>Title</h2>
<div class="stacking__content">
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Consequatur error, reiciendis enim nulla voluptate non aspernatur rem dicta alias perspiciatis qui nobis ratione similique incidunt magnam ut commodi quae eaque!</p>
</div>
</div>
</div>
<section class="container">
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Iusto eum iste est aspernatur dolorum corporis hic tenetur labore voluptas dolor vitae numquam consectetur autem obcaecati quisquam ipsam eveniet, ad veritatis.</p>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Iusto eum iste est aspernatur dolorum corporis hic tenetur labore voluptas dolor vitae numquam consectetur autem obcaecati quisquam ipsam eveniet, ad veritatis.</p>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Iusto eum iste est aspernatur dolorum corporis hic tenetur labore voluptas dolor vitae numquam consectetur autem obcaecati quisquam ipsam eveniet, ad veritatis.</p>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Iusto eum iste est aspernatur dolorum corporis hic tenetur labore voluptas dolor vitae numquam consectetur autem obcaecati quisquam ipsam eveniet, ad veritatis.</p>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Iusto eum iste est aspernatur dolorum corporis hic tenetur labore voluptas dolor vitae numquam consectetur autem obcaecati quisquam ipsam eveniet, ad veritatis.</p>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Iusto eum iste est aspernatur dolorum corporis hic tenetur labore voluptas dolor vitae numquam consectetur autem obcaecati quisquam ipsam eveniet, ad veritatis.</p>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Iusto eum iste est aspernatur dolorum corporis hic tenetur labore voluptas dolor vitae numquam consectetur autem obcaecati quisquam ipsam eveniet, ad veritatis.</p>
</section>
</main>
CSS 소스
@function pxToRem($pixel) {
@return $pixel / 16 + rem;
}
:root {
--black: #1f1f1f;
--white: #fff;
}
body {
background-color: var(--black);
color: var(--white);
}
main {
padding: pxToRem(50) 0;
}
h1 {
color: var(--white);
font-size: clamp(3.125rem, 17.321vw + -1.357rem, 12.5rem);
line-height: clamp(4.688rem, 21.363vw + -0.84rem, 16.25rem);
margin: pxToRem(100) 0;
text-align: center;
}
h2 {
font-size: clamp(1.5rem, 1.848vw + 1.022rem, 2.5rem);
line-height: clamp(2.25rem, 1.848vw + 1.772rem, 3.25rem);
}
.stacking {
position: relative;
}
.stacking__card {
background-color: var(--white);
border-radius: pxToRem(32);
color: var(--black);
display: flex;
flex-direction: column;
gap: var(--whatwedo-gap);
margin: 25vh auto;
max-width: pxToRem(1020);
padding: pxToRem(32) pxToRem(32) 0;
}
.stacking__content {
font-size: pxToRem(18);
margin: pxToRem(20) 0;
padding-bottom: pxToRem(20);
}
.container {
max-width: pxToRem(800);
margin: 0 auto;
}
p {
margin: pxToRem(20) 0;
}
JS 소스
gsap.registerPlugin(ScrollTrigger, ScrollSmoother);
ScrollSmoother.create({
smooth: 1,
effects: true,
normalizeScroll: true
});
const cards = gsap.utils.toArray(".stacking__card");
const spacer = 50;
cards.forEach((card, index) => {
ScrollTrigger.create({
trigger: card,
start: `center-=${index * spacer} center`,
endTrigger: ".stacking",
end: `bottom center`,
pin: true,
pinSpacing: false,
// markers: true,
invalidateOnRefresh: true
});
const scaleValue = 0.85 + index * 0.05;
gsap.to(card, {
scrollTrigger: {
trigger: card,
start: `top center`,
end: `bottom center`,
scrub: true,
// markers: true,
invalidateOnRefresh: true
},
// ease: "none",
scale: scaleValue
});
});
