Scroll-driven animation

스크롤 기반 애니메이션

A native CSS feature that wires scroll progress directly into an animation's timeline — no JavaScript. animation-timeline is the key property.

Also known as: CSS scroll-timelineanimation-timeline: scroll()
···
html
<div class="demo">
  <div class="scroller" id="scroller">
    <div class="content">
      <div class="mark">●</div><div class="mark">●</div><div class="mark">●</div><div class="mark">●</div><div class="mark">●</div>
    </div>
  </div>
  <div class="bartrack"><div class="bar" id="bar"></div></div>
  <code class="label">animation-timeline: scroll()</code>
</div>
css
.demo{position:absolute;inset:8%;display:grid;grid-template-rows:1fr auto auto;gap:10px}
.scroller{position:relative;overflow-y:scroll;border:1px solid var(--line);border-radius:10px;
  scroll-timeline:--st block;scrollbar-width:none}
.scroller::-webkit-scrollbar{display:none}
.content{height:320%;display:flex;flex-direction:column;justify-content:space-between;padding:16px 0}
.mark{color:var(--muted);font-size:12px;text-align:center}
.bartrack{height:8px;border-radius:6px;background:var(--line);overflow:hidden}
.bar{height:100%;width:100%;transform:scaleX(0);transform-origin:left;background:var(--accent);
  animation:grow linear;animation-timeline:--st}
@keyframes grow{from{transform:scaleX(0)}to{transform:scaleX(1)}}
.label{font:10px/1 ui-monospace,monospace;color:var(--muted);text-align:center}
js
const el = document.getElementById('scroller');
const bar = document.getElementById('bar');
let dir = 1;
function loop() {
  const max = el.scrollHeight - el.clientHeight;
  el.scrollTop += dir * 1.2;
  if (el.scrollTop >= max) dir = -1;
  if (el.scrollTop <= 0) dir = 1;
  // 브라우저가 animation-timeline 으로 이미 갱신 중이면 이 값은 같은 결과를 한 번 더 쓸 뿐이고,
  // 아직 지원하지 않는 브라우저에서는 이 값이 유일한 진행 표시가 된다.
  const progress = max > 0 ? el.scrollTop / max : 0;
  bar.style.transform = 'scaleX(' + progress + ')';
  requestAnimationFrame(loop);
}
requestAnimationFrame(loop);

Previously, tying anything to scroll meant listening to the scroll event and updating styles in requestAnimationFrame. animation-timeline replaces the animation's progress source — instead of time, it's driven by scroll offset. The @keyframes stay the same; only what drives the playhead changes.

A reading-progress bar becomes a few lines of CSS. With a view() timeline, an element's animation progress is instead driven by how far it has crossed the viewport — the native version of scroll-reveal.

As of 2026, Chrome/Edge support it, but Safari/Firefox coverage is still partial, so production code usually keeps a JS fallback alongside it.

When to use

Use it where you control the target browsers, for lightweight progress bars and scroll-linked effects. If you need broad support, keep a JS scroll-listener fallback.