v149 · CSS · SVG · Scroll

Scroll Progress

CSS path-length makes it trivial to drive stroke-dashoffset from a scroll listener — set path-length: 100 once and map scroll percentage directly to stroke-dashoffset. No measuring circle circumferences, no fragile constants.

Checking path-length CSS support…
scroll progress 0%
0%

Why path-length matters

Before Chrome 149, implementing a stroke-based scroll progress bar required JavaScript to compute the exact geometric length of the SVG path — for a circle of radius 22, that is 2π × 22 ≈ 138.2px. You then set stroke-dasharray to that number and animated stroke-dashoffset from 138.2 down to 0. The magic number 138.2 was fragile: change the circle size, change the number everywhere. Forget to update? Broken animation.

The Chrome 149 approach

With path-length: 100 set in CSS (Chrome 149+), the browser normalizes the path so that its total length equals 100 user units. Now stroke-dasharray: 100 always covers the full path, and stroke-dashoffset goes from 100 (empty) to 0 (full). The scroll listener just does: fillPath.style.strokeDashoffset = 100 - scrollPercent. No measurements. No magic numbers. Resize the shape? Nothing breaks.

Before (Chrome < 149) — fragile
/* JS must measure the path */ const len = path.getTotalLength(); // → 138.23px for r=22 circle path.style.strokeDasharray = len; path.style.strokeDashoffset = len; // scroll listener: const offset = len - (len * pct); path.style.strokeDashoffset = offset;
After (Chrome 149+) — simple
/* CSS only — no JS measurement */ circle { path-length: 100; stroke-dasharray: 100; stroke-dashoffset: 100; } // scroll listener: path.style.strokeDashoffset = 100 - scrollPercent;

Works with any shape

The path-length CSS property works with any SVG shape that accepts a stroke: <path>, <circle>, <rect>, <line>, <polyline>, <polygon>, <ellipse>. Set path-length: 100 and the browser handles normalization for all of them, even complex multi-segment paths.

scroll: 0% | stroke-dashoffset: 100.00 | linear fill-path d="M 4 10 L 396 10"

Custom properties integration

Because path-length is a CSS property, it composes with custom properties. You can define --pl: 100 and use it in path-length: var(--pl), stroke-dasharray: var(--pl), and stroke-dashoffset: var(--pl) — a single variable drives all three declarations. Change the variable in one place to switch between normalized scales.

Scroll-timeline alternative

For browsers that support scroll-driven animations (animation-timeline: scroll()), you can animate stroke-dashoffset entirely in CSS without any JavaScript. The path-length property works alongside that approach too: the @keyframes simply goes from stroke-dashoffset: 100 to stroke-dashoffset: 0. Two Chrome features that compose cleanly.

Keep scrolling…

Scroll to the bottom of this page to see the progress indicators reach 100%. Both the linear bar at the top and the circular indicator in the top-right corner are updated by the same scroll listener, each using path-length: 100 so the offset calculation is identical for both shapes despite them having very different geometric lengths.

End of content

You reached the bottom. Both progress indicators should now show 100%. Scroll back up to see them reset. The stroke-dashoffset transitions ensure smooth movement even when scroll speed varies.

see also

implementation reference

Need the exact API surface, compatibility boundaries, errors, lifecycle, and source links? Read the matching gendn reference ↗