scroll()

Saleh Mubashar on

Get affordable and hassle-free WordPress hosting plans with Cloudways — start your free trial today.

Experimental: Check browser support before using this in production.

The scroll() function provides an anonymous scroll progress timeline based on a scroller’s position. Unlike the view() function, which animates based on an element’s position in a scrollport, the scroll() function animates based on how far elements have been scrolled along a given axis.

span {
  animation: text 1s steps(var(--n, 100)) forwards;
  animation-timeline: scroll(root);
}

@keyframes text {
  0% {
    background-size: 0;
  }
}

The scroll timeline is then used to track the user’s scroll position and determine the scroll progress accordingly. In other words, the animation is based on the user’s scroll position instead of the default timeline.

The scroll() function is defined in the Scroll-driven Animations.

Syntax

Remember the scroll() function can only be used with the CSS animation-timeline property.

animation-timeline: scroll(<scroller> <axis>);

Arguments

/* Default behavior */
animation-timeline: scroll();

/* Just the scroller */
animation-timeline: scroll(nearest); /* Closest scrollable ancestor */
animation-timeline: scroll(root); /* The document itself */
animation-timeline: scroll(self); /* The element itself (if scrollable) */

/* Just the axis */
animation-timeline: scroll(block); /* Perpendicular to text flow */
animation-timeline: scroll(inline); /* Parallel to text flow */
animation-timeline: scroll(y); /* Explicitly vertical */
animation-timeline: scroll(x); /* Explicitly horizontal */

/* Both parameters */
animation-timeline: scroll(root y);

scroller

  • nearest(default): The closest parent element with a scrollbar
  • root: The entire page (document)
  • self: The element itself, if it’s scrollable

axis

  • block (default): Scroll axis in the block direction (usually vertical for horizontal writing).
  • inline: Scroll axis in the inline direction (typically horizontal for horizontal writing).
  • y: Vertical scroll axis.
  • x: Horizontal scroll axis.

If you don’t provide the function with any parameters, it will automatically use the nearest scrollable ancestor (i.e., scroller) and track scrolling in the block direction by default. If a specified axis is not a scroll container, i.e, it does not have a scrollbar, the scroll() function will not work.

Behind the scenes, scroll() animations do the following:

  • The function starts from the element being animated and moves up the DOM tree.
  • It looks for the first parent element that has a scrollbar.
  • That scroll container’s position is then used to calculate the scroll progress. This scroll container is referred to as the scroller.
  • The default scroll axis, block, means the direction that is perpendicular to the text flow is used. For most Western languages (those that flow horizontally), this results in a vertical scroll axis.

The old way

What could be more basic than the popular scroll watcher animation? You’ve probably come across a progress element fixed at the top of the page, whose progress increases as you scroll down a webpage. This progress element was formerly achieved with the aid of JavaScript.

window.addEventListener("scroll", () => {
  const scrollTop = document.documentElement.scrollTop;
  const scrollHeight = document.documentElement.scrollHeight;
  const clientHeight = document.documentElement.clientHeight;

  const scrolled = (scrollTop / (scrollHeight - clientHeight)) * 100;

  document.querySelector(".scroll-watcher").style.width = `${scrolled}%`;
});

document.documentElement.scrollTop shows how much you’ve scrolled, scrollHeight - clientHeight gives the total scrollable height, and then you calculate the percentage and apply it to the .scroll-watcher width.

.scroll-watcher {
  height: 6px;
  width: 0%;

  transition: width 0.1s linear;
}

Basic Usage

Not anymore! With just CSS, using the scroll() function, you can achieve the same effect:

scroll-watcher {
  height: 6px;
  width: 0;

  animation: scroll-watcher linear;
  animation-timeline: scroll();
  animation-duration: auto;
}

@keyframes scroll-watcher {
  to {
    width: 100%;
  }
}

Note: When using the animation shorthand, make sure to declare animation-timeline after it. The shorthand resets animation-timeline to auto, so any earlier value gets overridden.

Examples!

Let’s look at a quick example. Here’s a red circle that fades in and scales up as you scroll down the page. This version uses the default behavior, so we’re not passing any arguments to scroll():

The animation is driven by vertical scrolling of the document, which in this case is also the nearest scrollable ancestor. So, the default scroll() behavior works out of the box. The block axis resolves to vertical scrolling since the text flows horizontally.

animation-name: fadeScale;
animation-duration: 1ms; /* Firefox requires this */
animation-timeline: scroll();

We can play around with the axis. For example, in this case, the animation is tied to horizontal scrolling instead of vertical (despite it also being present). If you set the axis to block or leave the axis parameter empty, then the animation would instead follow the vertical scrollbar by default.

Demos

I’ll be providing examples in this section, as the goal is to understand how scroll()works. However, you can check out a bunch of awesome and pretty practical examples and implementations over at Bramus’s demo site.

Scroll progress bar

One of the simplest — and most effective — ways to use a scroll timeline is for a progress bar that tracks how far you’ve scrolled. In this setup, the width of the bar is directly tied to the document’s scroll progress. Since we’re tracking vertical scrolling on the root (the whole page), the default behavior of scroll() works great out of the box.

Parallax effect

This example uses a scroll-driven animation to create a simple parallax effect. As you scroll, the .content section shifts upward by adjusting its margin. It’s a basic demo, but shows the potential for similar more complex effects.

Text reveal

You’ve likely seen text that reveals itself as you scroll—like a typewriter effect. We can recreate this using a scroll-driven animation that gradually changes the background size of a linear gradient. As the background expands, each character appears to be “colored in.” Using steps() and ch units makes the reveal snap cleanly, one character at a time.

Scroll Shadows

Another subtle way you can incorporate the view timeline is by adding scroll shadows, especially on mobile. There’s a separate article that covers this in much more detail, but in short, here’s how it works:

This technique uses animation-timeline: scroll() with custom properties (--left-fade, --right-fade) and a CSS mask to fade out the edges of a scrollable container. The scroll progress controls how much of the fade is applied to the left and right edges of the element.

view() vs. scroll()

view() Functionscroll() Function
PurposeTracks an element’s visibility progress within a specific view (e.g., viewport or scroll container).Tracks scroll progress of a scroll container (e.g., viewport or overflow element).
Syntaxview([axis] [inset])scroll([scroller] [axis])
Main Use CaseAnimates based on how much of an element is visible in a view timeline.Animates based on the scroll position of a scroll container.
Timeline TypeView Timeline: Tied to an element’s entry/exit in a view.Scroll Timeline: Tied to the scroll position of a container.
LimitationsLimited to visibility-based animations; requires a subject element.Tracks the scroll progress of a scroll container (e.g., viewport or overflow element).

Browser support

The scroll() function currently has limited availability and it’s walled behind experimental flags in some browsers. You can check for support and add some fallback using the snippet below:

@supports (animation-timeline: scroll()) {
  .scroll-watcher {
    animation-timeline: scroll();
    animation-duration: auto;
  }
}

Specification

The CSS scroll() function is defined in the Scroll-Driven Animations specification which is currently in Editor’s Draft status as of June 2025. That means the information in the specification can change between now and when the feature becomes a formal Candidate Recommendation, so refer to the Browser Support section before using it in a production environment.