Circular progress
A ring in the corner fills with your scroll position and doubles as a back-to-top button.
A thin bar across the top fills as you move through an article.
In browsers with scroll-driven animations the bar is pure CSS: animation-timeline: scroll(root) scales it from 0 to 1. A four-line fallback script does the same with scrollY in other browsers.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Reading progress bar</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; }
body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; -webkit-font-smoothing: antialiased; }
body { background: #fff; color: #1d1d1f; }
.bar {
position: fixed; inset: 0 0 auto 0; height: 5px; background: #ff3d6e;
transform-origin: 0 50%; transform: scaleX(0); z-index: 10;
}
@supports (animation-timeline: scroll()) {
.bar { animation: fill linear both; animation-timeline: scroll(root); }
}
@keyframes fill { to { transform: scaleX(1); } }
.post { max-width: 640px; margin: 0 auto; padding: 14vh 24px 30vh; font: 1.2rem/1.7 Georgia, serif; }
.post h1 { font: 800 2.8rem/1.05 system-ui, sans-serif; letter-spacing: -.03em; }
.post p { margin: 0 0 1.6em; }
</style>
</head>
<body>
<div class="bar" id="bar"></div>
<article class="post">
<h1>Why scrolling won</h1>
<p>Early web pages were short. Designers borrowed from print and broke long text into pages with numbered links at the bottom.</p>
<p>Then mice grew wheels, laptops grew trackpads, and phones made the thumb flick the most common gesture in computing. Scrolling stopped being a chore and became the way people read.</p>
<p>Long pages have a cost: it is easy to lose your place, and hard to tell how much is left. A progress bar answers the second question with one line of colour.</p>
<p>It should be quiet. Thin, at the very top, in an accent colour that does not compete with the text.</p>
<p>It should be honest. The bar measures the whole document, so a long comment section below the article will make the article itself feel shorter than it is. Measure the article if that matters.</p>
<p>And it should be cheap. With scroll-driven animations, the browser handles it entirely on the compositor, with no script running on scroll.</p>
<p>That is the whole idea. Keep reading to watch the bar fill.</p>
<p>Almost there.</p>
<p>Done.</p>
</article>
<script>
if (!CSS.supports('animation-timeline: scroll()')) {
const bar = document.getElementById('bar');
const update = () => {
const max = document.documentElement.scrollHeight - innerHeight;
bar.style.transform = 'scaleX(' + (max > 0 ? scrollY / max : 0) + ')';
};
addEventListener('scroll', update, { passive: true });
update();
}
</script>
</body>
</html>