Native Scroll Timelines Are Here: Does Every Scroll Effect Still Need JavaScript?

CSS scroll-driven animations can replace JavaScript for many scroll effects, but not all. See where native timelines fit, where JS still earns its place, and how to choose.

native-scroll-timelines
Hitesh Bhardwaj

Hitesh Bhardwaj

Sr. Developer

Read Time: 8 mins

Category: Engineering

Share this Article:

A reading-progress bar has accumulated a surprising amount of JavaScript over the years.

Listen for scroll. Read the offset. Calculate a percentage. Write a transform. Throttle the handler or move the work into requestAnimationFrame. Remove the listener when the component unmounts.

CSS scroll-driven animations remove that machinery from a useful class of effects. The browser can use scroll or view progress as the timeline for CSS keyframes, so progress indicators, restrained reveals and simple parallax treatments no longer need application code translating pixels into animation progress.

The short answer is still not “JavaScript is dead.”

Native CSS is a strong first implementation to test when scroll progress maps directly to presentation. JavaScript still earns its place when the interaction depends on direction, velocity, callbacks, application state, complex sequencing, canvas or WebGL rendering, or compatibility requirements that need scripted behavior.

The useful question is no longer “CSS or JavaScript?” It is: what does this particular interaction actually need?


What CSS scroll-driven animations actually changed

Traditional CSS animations run against time. A 2s animation progresses because two seconds pass.

Scroll-driven animations replace that clock with scroll progress. animation-timeline can connect CSS keyframes to either a scroll progress timeline or a view progress timeline. scroll() tracks a scroll container; view() tracks an element as it moves through its scrollport.

A reading-progress indicator can therefore be almost suspiciously small:

@keyframes reading-progress {
  from {
    transform: scaleX(0);
  }

  to {
    transform: scaleX(1);
  }
}

.reading-progress {
  position: fixed;
  inset: 0 0 auto;
  height: 3px;
  transform-origin: left;

  animation: reading-progress 1ms linear;
  animation-timeline: scroll(root block);
}

There is no scroll listener calculating scrollY / scrollHeight. The browser already owns the scroll position; CSS is simply using it as the timeline.

The slightly odd 1ms duration is intentional. Scroll progress still controls the effective animation progress, but MDN currently recommends a non-zero duration because Firefox requires one for the animation to apply correctly.

There is another copy-paste trap: the animation shorthand resets animation-timeline to auto, so the timeline declaration should come after the shorthand.

A view-based reveal uses the same model:

@keyframes reveal {
  from {
    opacity: 0;
    transform: translateY(2rem);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.reveal {
  animation: reveal 1ms linear both;
  animation-timeline: view();
  animation-range: entry 10% cover 35%;
}

Here, document progress is irrelevant. The animation follows the element's journey through the viewport.

That covers a useful slice of creative frontend work: progress bars, image scaling, restrained parallax and section treatments where scroll progress maps directly to CSS-animatable presentation. A progress bar should not require a small event-listener bureaucracy.


Scroll-driven and scroll-triggered are different problems

Suppose a card enters the viewport and then fades in over 400 milliseconds. Scroll determines when the animation starts. Time determines how the animation progresses after that.

Now take an image that moves from translateY(-10%) to translateY(10%) in exact proportion to its journey through the viewport. Scroll determines the animation's progress continuously. Those are different control models.

A scroll-driven animation handles the second case: progress is tied to scrolling. A scroll-triggered animation starts, pauses, resets or otherwise controls a normal time-based animation when a scroll boundary is crossed.

Historically, that trigger case often meant IntersectionObserver. As of Chrome 146, Chromium also supports declarative CSS scroll-triggered animations using timeline triggers and animation triggers, including timeline-trigger and animation-trigger. Chrome explicitly positions the feature as a CSS alternative for many interactions previously built with viewport-detection JavaScript.

That does not make IntersectionObserver obsolete. Current compatibility data shows animation-trigger support in Chromium-family browsers from version 146, while Firefox and Safari do not yet support it. For a cross-browser one-shot reveal today, an observer may still be the simpler production choice.

So there are now three distinct questions:

JobNatural starting point
Progress follows scrolling continuouslyscroll() / view() timeline
Crossing a scroll boundary starts a time-based animationCSS scroll trigger where support fits; otherwise IntersectionObserver
Scroll changes application state or executes richer logicJavaScript

Treating all three as “scroll animation” is how implementation decisions get muddy.


Where native CSS should be the first implementation to test

The strongest native candidates share one trait: scroll position is already the state.

A long-form article progress line needs one value: how far through the document the reader has travelled. A product image that scales from 1.08 to 1 while entering the viewport has a similarly direct relationship. View progress maps to scale. The same applies to restrained parallax or an opacity treatment whose only job is to move between visual states across a defined range.

InteractionNative CSS fitWhyTypical fallback
Reading progress indicatorStrongScroll progress is the entire inputHide the decorative indicator
View-linked revealStrongView progress maps directly to keyframesRender content fully visible
Modest image parallaxStrongOne continuous value drives a transformStatic image
Scale/opacity treatmentStrongDeclarative range expresses the behaviorFinal readable state
Scroll direction-sensitive headerWeakBehavior depends on direction and retained stateJavaScript logic
Velocity-reactive motionWeakCurrent speed affects outputJavaScript/library logic
Multi-scene pinned narrativeDependsSequencing and pinning can exceed simple timelinesSimplified vertical flow
WebGL scene controlWeakScroll must update rendering stateStatic/poster or simplified scene

The threshold is not visual ambition. A polished effect can still be simple under the hood.

The better test is:

When progress moves from A to B, can the effect be described as animating presentation values from X to Y?

When the answer is yes, native CSS deserves the first attempt.


Where JavaScript still earns its place

Scroll position is not always enough information.

Consider a navigation bar that disappears while the user scrolls down and returns when they reverse direction. The important value is not only where the page is. The interaction needs to know which way the user is moving.

A kinetic card stack might respond more strongly to a fast flick than a slow scroll. Now velocity matters.

A product story might change React state when the reader enters chapter three, update an active navigation item, swap media sources and record an analytics event. Scroll is coordinating application behavior, not merely changing presentation.

Those stateful requirements still belong naturally in JavaScript.

Mature animation tooling also remains useful when an interaction needs scrubbed multi-step timelines, snapping, callbacks, velocity, direction or advanced pinning. GSAP ScrollTrigger currently exposes those controls directly, including getVelocity(), direction, snapping, callbacks and pinned ranges.

A pinned feature sequence is a good example. One panel may stay in place while child timelines enter, overlap and exit at deliberate points. Labels may define chapters. Snapping may pull scrolling toward stable states. Resizing may change calculated ranges.

You can construct parts of that system with CSS, sticky positioning and native timelines. Whether that is the better implementation depends on whether the resulting composition is simpler than the orchestration it replaces.

Native APIs are building blocks, not a purity test.

Canvas and WebGL make the boundary clearer

CSS can animate the position, opacity or transform of a <canvas> element. It does not replace the rendering system inside that canvas.

If scrolling moves a Three.js camera through a scene, changes shader uniforms, morphs geometry or controls postprocessing, application code still has to translate interaction state into rendering state.

In React Three Fiber, that may involve refs, hooks, animation values or a render loop. Production quality also means cleaning up listeners and timelines, pausing expensive work when a scene is hidden or offscreen, and reducing rendering cost where mobile hardware does not justify the desktop treatment.

Meaningful copy, links and calls to action should remain in normal HTML rather than becoming dependent on a decorative canvas. A shader does not become CSS because the page has learned a new timeline.


Native does not automatically mean faster in every case

Scroll-driven CSS has a meaningful architectural advantage: the browser owns both scrolling and the animation timeline.

That can avoid a synchronization problem where main-thread JavaScript repeatedly reads scroll state and tries to keep visual updates aligned with browser scrolling. It is still worth avoiding the usual performance slogans.

A native timeline does not turn every animated property into a cheap one. Layout-heavy properties can still create more work than transforms or opacity. Large paint areas remain large paint areas. Several complex effects can still compete for rendering resources. The performance path also depends on the browser implementation. Safari 26.4 added threaded scroll-driven animations, allowing eligible scroll-driven animations to run on the compositor thread separately from the main thread. That is a concrete implementation improvement. It is not proof that every native scroll animation in every browser is automatically off-main-thread.

Profile the page you intend to ship. Test a production build, inspect rendering and main-thread activity in browser DevTools, and check real devices before turning “native” into a performance claim.


Browser support still changes the production decision

Native scroll timelines are useful, but they are not yet a universal baseline.

MDN currently marks animation-timeline as Limited availability. Firefox 155 contains scroll-driven animations as an experimental feature, enabled by default in Nightly but disabled by default in Release, Beta and Developer Edition behind layout.css.scroll-driven-animations.enabled. MDN: animation-timeline browser status Mozilla: Firefox 155 release notes

That makes progressive enhancement more important than API enthusiasm.

Start from a useful static state:

.reveal {
  opacity: 1;
  transform: none;
}

@supports (animation-timeline: view()) {
  .reveal {
    animation: reveal 1ms linear both;
    animation-timeline: view();
    animation-range: entry 10% cover 35%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .reveal {
    animation: none;
    opacity: 1;
    transform: none;
  }
}

A browser without timeline support receives readable content instead of copy stuck at opacity: 0. That is often enough. A decorative reveal does not necessarily deserve a JavaScript polyfill merely to preserve motion everywhere.

Functional behavior is different. If scroll state drives navigation, content selection or another essential interaction, unsupported browsers need a real alternative path rather than a missing effect. The animation's job decides how expensive its fallback needs to be.


Reduced motion is only part of accessibility

prefers-reduced-motion matters, but it is not the entire accessibility strategy.

A robust scroll interaction should still make sense when its motion disappears.

Keep content in a logical DOM order. Keyboard users should be able to reach links, controls and sections without depending on a pinned sequence. Required information should not exist only inside a transform, canvas, hover state or animated transition.

For scroll storytelling, the static document should still communicate the story. That may mean collapsing a pinned sequence into a normal vertical flow on smaller screens or reduced-motion configurations. It may mean showing the final readable state immediately instead of replaying a reveal. It may mean keeping semantic HTML outside a decorative WebGL layer.

Motion can control presentation. It should not become the only route to meaning.


React does not force you into either camp

A React project does not make a CSS effect less native.

If a component's scroll behavior is entirely presentational, React can render the markup and CSS can own the timeline. In Next.js, that component can remain server-rendered if it otherwise has no client-side behavior. Once the interaction needs React state, DOM measurement, browser APIs, animation hooks or a JavaScript engine, the boundary changes.

RequirementLikely implementation
CSS-only scroll() or view() effectServer-rendered markup + CSS can be enough
Cross-browser viewport triggerClient-side observer or another fallback
Motion useScroll valuesClient component
GSAP ScrollTriggerClient component with lifecycle cleanup
Canvas or WebGL sceneClient-side rendering path in most cases
Decorative unsupported-browser fallbackStatic server-rendered content + progressive enhancement

Libraries can also sit between pure CSS and fully scripted control.

Motion's current React documentation says its scroll-linked animations use the browser's native ScrollTimeline where possible and fall back to JavaScript when necessary. Its useScroll API exposes scroll progress for composition inside React.

So “library” and “native” are no longer opposites in every implementation.

The architectural question is whether the abstraction is buying something: compatibility, composition, state integration, sequencing or a consistent API across several effects. If it is wrapping three lines of CSS without adding any of those things, the wrapper has some explaining to do.


A four-question test for your next scroll effect

Before adding another scroll dependency, run the interaction through four questions.

1. Can CSS express the output?

If the result is a direct animation of transform, opacity, clip-path or another CSS-animatable presentation value, test a native timeline first.

2. Does the behavior need more than progress?

Direction, velocity, callbacks, application state, analytics, dynamic measurements or other side effects are signs that JavaScript has a real job.

3. What happens when the timeline is unavailable?

If the page can fall back to a static readable state, progressive enhancement keeps native CSS attractive. If the scroll interaction controls essential functionality, design an explicit fallback.

4. Does a library reduce the actual complexity?

A mature animation library is not automatically excessive because it contains more capability than one CSS property. If the page already coordinates multiple timelines, pins, snap points, responsive recalculation and state changes, the abstraction may remove more complexity than it introduces.

If your interaction…Start here
Maps scroll/view progress directly to CSS presentationNative scroll() / view()
Starts a time-based animation at a scroll boundary and Chromium-only support is acceptableCSS scroll-triggered animation
Needs that trigger across current major browsersIntersectionObserver or a library fallback
Needs state, direction, velocity or callbacksJavaScript
Needs coordinated pinning, snapping and timelinesAnimation engine such as GSAP
Needs scroll to manipulate canvas/WebGL stateJavaScript/rendering layer

Choose the tool that leaves the least unnecessary orchestration after the real requirements are satisfied.


A small demo is enough to test the model

Before introducing a library, build one deliberately boring experiment:

  1. Add a fixed progress bar using scroll(root block).
  2. Add a content card using view().
  3. Turn JavaScript off.
  4. Confirm the page still reads correctly when the animation is unsupported.
  5. Enable reduced motion and confirm nothing essential disappears.
  6. Profile the production build before deciding whether the effect needs more machinery.

That six-step test exposes most of the architecture quickly. If the interaction already works, reads correctly and degrades cleanly, adding an engine should solve a problem you can name.

If it does not, you have found the reason JavaScript belongs there.


What this means for Vault scroll effects

Treating every scroll effect as though it needs the same engine would now be especially poor architecture.

Hyperiux Vault uses an effect-level dependency model. Some patterns rely on React and CSS. Others use GSAP, Motion, Three.js or additional rendering tools when their implementation requires those capabilities. The dependencies stay visible with the effect rather than being assumed for an entire category.

That distinction matters more as browsers absorb a larger share of straightforward scroll-linked presentation.

Take Vault's Parallax Image effect. Its current implementation uses GSAP for scrubbed image movement and optional scaling. Its effect page also documents mobile, reduced-motion and performance considerations rather than treating the demo as a universal implementation guarantee.

That does not mean every parallax effect needs GSAP.

A quiet image offset that only maps view progress onto translateY may now be an excellent native CSS candidate. A composed sequence with multiple ranges, state changes, responsive recalculation or timeline coordination has different requirements.

Editable source makes that choice visible.

The engine, timing, breakpoints, fallback and reduced-motion strategy can change with the page instead of becoming an installation-time assumption.


The rule is simpler than “CSS good, JavaScript bad”

Native scroll timelines have removed a lot of JavaScript that never needed to be interesting.

A progress indicator can stay close to the styles it changes. A view-linked effect can use the viewport as its timeline. Chrome's newer scroll-trigger API can even remove JavaScript from some threshold-based time animations when its browser support is sufficient.

JavaScript remains useful when the interaction becomes stateful, orchestrated or connected to a rendering system outside CSS. GSAP, Motion and custom logic still solve problems that scroll timelines were not designed to absorb.

So start one level lower than you used to.

Use the browser when progress maps directly to presentation. Escalate when the interaction gives you a concrete reason: state, direction, velocity, callbacks, choreography, rendering or compatibility.

Related Blogs

how-to-build-scroll
Engineering
September 23, 2026

How to Build Scroll Animations in React Without Turning the Page Into a Timeline Mess

Build maintainable scroll experiences by giving each section its own trigger, progress source, or scoped timeline instead of coupling the entire page to one master timeline.

prebuilt-vs-custom
Engineering
September 23, 2026

Prebuilt Interaction Patterns vs Hand-Built Animations in React

Choose between native APIs, animation libraries, and reusable source-first patterns based on how much control, orchestration, and project-specific adaptation the interaction actually requires.

css-animation-featured
Engineering
September 23, 2026

When CSS Animation Is Enough and When React Needs a Library

Learn where CSS ends and animation libraries begin by matching the tool to the interaction- using CSS for visual states and Motion or GSAP when gestures, layout, presence, or complex choreography require more control.

hidden-complexity-of-page-transition
Engineering
September 25, 2026

The Hidden Complexity of Page Transitions in the Next.js App Router

Next.js page transitions look simple until routing, Suspense, layouts and accessibility collide. Learn what App Router transitions actually require in production.

how-to-build-reusable-react-animations
Engineering
September 25, 2026

How to Build Reusable React Animation Components Without Making Every Effect Config-Heavy

Build reusable React animation components without prop explosion. Learn what belongs in props, presets, composition, shared defaults, and source-level edits.