<section class="demo">
<div class="demo__grid">
<div class="demo__stat"><div class="demo__value" data-value="248"></div><p>Snippets</p></div>
<div class="demo__stat"><div class="demo__value" data-value="96"></div><p>Studios</p></div>
<div class="demo__stat"><div class="demo__value" data-value="17"></div><p>Awards</p></div>
</div>
</section>.demo {
width: 100%;
min-height: 100dvh;
display: grid;
place-items: center;
padding: 32px 24px;
background: #08080a;
color: #f4f4f2;
font-family: "Helvetica Neue", Inter, system-ui, sans-serif;
}
.demo__grid {
display: flex;
gap: clamp(20px, 7vw, 56px);
}
.demo__stat p {
margin: 10px 0 0;
font-size: 10px;
letter-spacing: 0.22em;
text-transform: uppercase;
color: #5a5a62;
}
.demo__value {
display: flex;
font-size: clamp(38px, 10vw, 68px);
font-weight: 500;
letter-spacing: -0.04em;
line-height: 1;
}
/* 자리마다 0~9가 세로로 늘어선 릴을 만들고, 창만 보여줍니다 */
.demo__reel {
height: 1em;
overflow: hidden;
}
.demo__reel i {
display: block;
height: 1em;
font-style: normal;
font-variant-numeric: tabular-nums;
}gsap.registerPlugin(ScrollTrigger);
const DIGITS = "0123456789".split("");
gsap.utils.toArray(".demo__value").forEach((el) => {
const target = String(el.dataset.value);
// 자리 수만큼 릴을 만듭니다.
el.innerHTML = target
.split("")
.map(() => "<span class=\"demo__reel\">" + DIGITS.map((d) => "<i>" + d + "</i>").join("") + "</span>")
.join("");
const tl = gsap.timeline({ scrollTrigger: { trigger: el, start: "top 90%" } });
el.querySelectorAll(".demo__reel").forEach((reel, index) => {
// 릴 안의 숫자를 통째로 같은 만큼 올리면 한 칸씩 굴러갑니다.
tl.to(
reel.children,
{
yPercent: -100 * Number(target[index]),
duration: 1.6,
ease: "expo.inOut",
},
index * 0.12
);
});
});