SolutionsCase StudiesAI ToolsResourcesAboutContact
Get Your Free AI Automation Audit Book a Free 30-Minute Strategy Call
Playground~/frontend Open editor

~/css/scroll-driven-animations

7 CSS animation-timeline Demos

7 Pure CSSPublished 2026-10-05Free · no sign-up

Seven demos that explain CSS scroll-driven animations: scroll() and view() side by side, cards that fade in on entry and out on exit, a named timeline shared across siblings, a horizontal timeline, words that light up as you read, an animation that runs while a section is pinned and view-timeline-inset.

01. scroll() and view() Side by Side

animation-timelineBasics

The two timelines in one panel: the bar at the top uses scroll(), so it tracks how far the whole container has scrolled, while each card uses view(), so it tracks its own trip through the visible area. Seeing both at once makes the difference obvious.

View code
<div class="sda1" tabindex="0" role="region" aria-label="Scroll this panel"><div class="bar" aria-hidden="true"></div><p>scroll(): the bar. view(): each card.</p><div class="c">Card A</div><div class="c">Card B</div><div class="c">Card C</div></div>
.sda1 { position: relative; width: 270px; max-width: 100%; height: 280px; overflow-y: auto; padding: 0 14px 14px; box-sizing: border-box; border: 1px solid #e4e7ec; border-radius: 16px; background: #fff; font-family: Inter, system-ui, sans-serif; }
.sda1:focus-visible { outline: 3px solid #84adff; outline-offset: 2px; }
.sda1 .bar { position: sticky; top: 0; height: 6px; margin: 0 -14px; background: #7f56d9; transform-origin: left; transform: scaleX(.05); z-index: 1; }
.sda1 p { height: 140px; margin: 10px 0 0; color: #475467; font-size: 14px; }
.sda1 .c { margin-bottom: 70px; padding: 26px; border-radius: 12px; background: #f4ebff; color: #53389e; font-weight: 800; }
@supports (animation-timeline: scroll()) {
  .sda1 .bar { animation: sda1b linear both; animation-timeline: scroll(nearest); }
  .sda1 .c { animation: sda1c linear both; animation-timeline: view(); animation-range: entry 0% cover 50%; }
  @keyframes sda1b { from { transform: scaleX(0); } to { transform: scaleX(1); } }
  @keyframes sda1c { from { opacity: .2; transform: translateX(-30px); } }
}

02. Fade In on Entry, Out on Exit

animation-timelineanimation-range

One keyframe set with four stops: hidden, visible, visible, hidden. With animation-range: entry 0% exit 100%, the first quarter plays as the card enters and the last quarter as it leaves, so cards fade in at the bottom and out at the top.

View code
<div class="sda2" tabindex="0" role="region" aria-label="Scroll this panel"><div class="c">1</div><div class="c">2</div><div class="c">3</div><div class="c">4</div><div class="c">5</div></div>
.sda2 { width: 240px; max-width: 100%; height: 280px; overflow-y: auto; padding: 120px 14px; box-sizing: border-box; border: 1px solid #2a3350; border-radius: 16px; background: #121828; font: 800 30px Inter, system-ui, sans-serif; }
.sda2:focus-visible { outline: 2px solid #f472b6; outline-offset: 2px; }
.sda2 .c { display: grid; place-items: center; height: 90px; margin-bottom: 16px; border-radius: 14px; background: #db2777; color: #fff; }
@supports (animation-timeline: view()) {
  .sda2 .c { animation: sda2 linear both; animation-timeline: view(); animation-range: entry 0% exit 100%; }
  @keyframes sda2 { 0% { opacity: 0; transform: scale(.8); } 25%, 75% { opacity: 1; transform: none; } 100% { opacity: 0; transform: scale(.8); } }
}
@media (prefers-reduced-motion: reduce) { .sda2 .c { animation: none; } }

03. Named Timeline Across Siblings

animation-timelinetimeline-scope

The animated element does not have to be inside the scroller. The list names its timeline with scroll-timeline: --sda3 y, a shared parent widens its reach with timeline-scope, and a counter badge outside the list rotates as the list scrolls.

View code
<div class="sda3"><div class="head"><b>Playlist</b><span class="disc" aria-hidden="true"></span></div><ol tabindex="0" aria-label="Playlist, scroll"><li>Morning Ferry</li><li>Harbour Lights</li><li>Saltwater</li><li>Low Tide</li><li>Lighthouse</li><li>North Sea</li><li>Coastline</li></ol></div>
.sda3 { box-sizing: border-box; width: 260px; max-width: 100%; border: 1px solid #e4e7ec; border-radius: 16px; background: #fff; overflow: hidden; font-family: Inter, system-ui, sans-serif; timeline-scope: --sda3; }
.sda3 .head { display: flex; align-items: center; justify-content: space-between; padding: 12px 14px; border-bottom: 1px solid #e4e7ec; color: #101828; }
.sda3 .disc { width: 34px; height: 34px; border-radius: 50%; background: radial-gradient(circle, #101828 0 4px, #f2f4f7 5px 7px, #344054 8px), conic-gradient(#344054 0 25%, #475467 0 50%, #344054 0 75%, #475467 0); }
.sda3 ol { height: 200px; overflow-y: auto; margin: 0; padding: 6px 14px 6px 34px; color: #344054; font-size: 14px; line-height: 2.6; scroll-timeline: --sda3 y; }
.sda3 ol:focus-visible { outline: 3px solid #84adff; outline-offset: -3px; }
@supports (animation-timeline: scroll()) { .sda3 .disc { animation: sda3 linear both; animation-timeline: --sda3; } @keyframes sda3 { to { transform: rotate(720deg); } } }
@media (prefers-reduced-motion: reduce) { .sda3 .disc { animation: none; } }

04. Horizontal Scroll Timeline

animation-timelineHorizontal

A horizontal gallery whose progress bar fills as you scroll sideways. scroll(x) is the only change from a vertical timeline. The gallery is a focusable region so keyboard users can scroll it with the arrow keys.

View code
<div class="sda4"><div class="g" tabindex="0" role="region" aria-label="Gallery, scroll sideways"><i style="--c:#f97316"></i><i style="--c:#eab308"></i><i style="--c:#22c55e"></i><i style="--c:#06b6d4"></i><i style="--c:#8b5cf6"></i><i style="--c:#ec4899"></i></div><div class="track" aria-hidden="true"><span></span></div></div>
.sda4 { width: 280px; max-width: 100%; timeline-scope: --sda4; }
.sda4 .g { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 8px; scroll-timeline: --sda4 x; scrollbar-width: none; }
.sda4 .g::-webkit-scrollbar { display: none; }
.sda4 .g:focus-visible { outline: 2px solid #a5b4fc; outline-offset: 2px; }
.sda4 i { flex: 0 0 120px; height: 150px; border-radius: 14px; background: var(--c); }
.sda4 .track { height: 4px; margin-top: 8px; border-radius: 2px; background: #2a3350; overflow: hidden; }
.sda4 .track span { display: block; height: 100%; background: #a5b4fc; transform-origin: left; transform: scaleX(.2); }
@supports (animation-timeline: scroll()) { .sda4 .track span { animation: sda4 linear both; animation-timeline: --sda4; } @keyframes sda4 { from { transform: scaleX(0); } to { transform: scaleX(1); } } }

05. Words Light Up as You Read

animation-timelineText

Each word of a statement brightens from dim to full as it scrolls through the middle of the panel, a popular storytelling effect. Every word is a span with its own view() timeline and a tight animation-range, so no script is involved.

View code
<div class="sda5" tabindex="0" role="region" aria-label="Statement, scroll"><p><span>We</span> <span>believe</span> <span>good</span> <span>software</span> <span>should</span> <span>feel</span> <span>calm,</span> <span>fast</span> <span>and</span> <span>honest.</span></p></div>
.sda5 { width: 260px; max-width: 100%; height: 280px; overflow-y: auto; padding: 140px 18px; box-sizing: border-box; border-radius: 16px; background: #0b0f1a; font: 800 30px/1.25 Inter, system-ui, sans-serif; }
.sda5:focus-visible { outline: 2px solid #fbbf24; outline-offset: 2px; }
.sda5 p { margin: 0; color: #f8fafc; }
@supports (animation-timeline: view()) {
  .sda5 span { animation: sda5 linear both; animation-timeline: view(); animation-range: cover 30% cover 50%; }
  @keyframes sda5 { from { color: #334155; } to { color: #fbbf24; } }
}

06. Animate While Pinned

animation-timelineSticky

A sticky box stays in place while its tall parent scrolls past, and during that stretch a shape inside it turns and changes colour. The animation's timeline is the tall parent's view() and its range is contain, the part where the parent fills the viewport.

View code
<div class="sda6" tabindex="0" role="region" aria-label="Scroll this panel"><p>Scroll ↓</p><section><div class="stick"><span class="shape" aria-hidden="true"></span><b>Pinned while it turns</b></div></section><p>After the pinned section.</p></div>
.sda6 { box-sizing: border-box; width: 260px; max-width: 100%; height: 280px; overflow-y: auto; border: 1px solid #e4e7ec; border-radius: 16px; background: #fff; font-family: Inter, system-ui, sans-serif; }
.sda6:focus-visible { outline: 3px solid #84adff; outline-offset: 2px; }
.sda6 > p { margin: 0; padding: 20px; height: 120px; color: #475467; font-size: 14px; }
.sda6 section { height: 700px; background: #f9fafb; view-timeline: --sda6 y; }
.sda6 .stick { position: sticky; top: 0; display: grid; place-items: center; gap: 10px; height: 280px; color: #101828; font-size: 14px; }
.sda6 .shape { width: 80px; height: 80px; border-radius: 18px; background: #2563eb; }
@supports (animation-timeline: view()) { .sda6 .shape { animation: sda6 linear both; animation-timeline: --sda6; animation-range: contain 0% contain 100%; } @keyframes sda6 { to { transform: rotate(270deg) scale(.7); border-radius: 50%; background: #db2777; } } }
@media (prefers-reduced-motion: reduce) { .sda6 .shape { animation: none; } }

07. Trigger Line with view-timeline-inset

animation-timelineInset

view-timeline-inset moves the edges the timeline measures against. Here an inset of 40 percent top and bottom means each bar only animates while it crosses a band in the middle of the panel, shown by two guide lines.

View code
<div class="sda7"><span class="g t" aria-hidden="true"></span><span class="g b" aria-hidden="true"></span><div class="sc" tabindex="0" role="region" aria-label="Scroll this panel"><i></i><i></i><i></i><i></i></div></div>
.sda7 { position: relative; width: 240px; max-width: 100%; height: 280px; border-radius: 16px; background: #121828; overflow: hidden; }
.sda7 .g { position: absolute; left: 0; right: 0; height: 1px; background: #f472b6; opacity: .6; z-index: 1; pointer-events: none; }
.sda7 .t { top: 40%; } .sda7 .b { bottom: 40%; }
.sda7 .sc { height: 100%; overflow-y: auto; padding: 140px 16px; box-sizing: border-box; }
.sda7 .sc:focus-visible { outline: 2px solid #f472b6; outline-offset: -2px; }
.sda7 i { display: block; height: 40px; margin-bottom: 40px; border-radius: 10px; background: #334155; transform-origin: left; }
@supports (animation-timeline: view()) { .sda7 i { view-timeline: --bar y; view-timeline-inset: 40%; animation: sda7 linear both; animation-timeline: --bar; animation-range: cover; } @keyframes sda7 { 0% { transform: scaleX(.3); background: #334155; } 50% { transform: none; background: #f472b6; } 100% { transform: scaleX(.3); background: #334155; } } }

About these CSS animation-timeline Demos

scroll() or view()

scroll() follows the scroll position of a container from top to bottom. view() follows one element's journey through the visible area, from entering to leaving. Most reveal effects want view(); progress bars want scroll().

Ranges choose the moment

animation-range picks part of the timeline: entry while the element enters, exit while it leaves, contain while it is fully visible and cover for the whole trip. Percentages refine each one.

Named timelines and scope

Name a timeline with scroll-timeline or view-timeline to use it on another element. If that element is not a descendant, declare the name with timeline-scope on a shared ancestor.

Always progressive

Wrap the rules in @supports (animation-timeline: view()) so browsers without support show the finished state. The animation should be a bonus, never the only way to see content.

Frequently asked questions

Do these work without JavaScript?

Yes. Every demo on this page is pure CSS.

Which browsers support animation-timeline?

Chromium browsers and Safari 26 at the time of writing; Firefox is working on it.

Why set animation-duration?

Scroll timelines ignore time, but some engines need a non-zero duration in the shorthand, so linear both with the default works best.