There is a way you can build a progress bar displaying how far a user has scrolled down the page (like a “reading progress indicator”) without any JavaScript at all. Just some clever usage of gradients and positioning.
There is a built-in browser feature for indicating your scroll position. Get this: it’s the scrollbar, and it does a great job. There is even a standardized way to style scrollbars these days.
body {
--scrollbarBG: #CFD8DC;
--thumbBG: #90A4AE;
scrollbar-width: thin;
scrollbar-color: var(--thumbBG) var(--scrollbarBG);
}
You might want to combine those with -webkit-
styles for the best browser support. For example:
html {
--scrollbarBG: #CFD8DC;
--thumbBG: #90A4AE;
}
body::-webkit-scrollbar {
width: 11px;
}
body {
scrollbar-width: thin;
scrollbar-color: var(--thumbBG) var(--scrollbarBG);
}
body::-webkit-scrollbar-track {
background: var(--scrollbarBG);
}
body::-webkit-scrollbar-thumb {
background-color: var(--thumbBG) ;
border-radius: 6px;
border: 3px solid var(--scrollbarBG);
}
![](png/styled-scrollbar2b0c.png)
But let’s say you weren’t as interested in styling the scrollbar as you were building your own indicator to show the user how far down they’ve scrolled. Like a progress bar that fills up as you approach the end of reading an article.
Mike Riethmuller found a way to do it that is extraordinarily clever!
It’s not only clever but is done with remarkably little code. To understand, let’s remove the white backgrounds on the header and the pseudo-element on the body revealing the linear-gradient
used.
![](png/gradient-on-bodyf002.png)
Ah ha! A diagonal gradient drawn with a hard stop. We can already see what’s happening here. As the page is scrolled down, the bit of this gradient that you can see becomes more and more filled with blue. The trick then becomes hiding everything but a small strip of this gradient, hence the solid backgrounds on the header and the pseudo-element, placed a few pixels apart.
![](png/gradient-gap8737.png)
Perhaps the most clever bit is how the gradient background is sized. You might think it just covers the entire background, but no. If you did that, the scrollbar would never complete because it is at the top of the page and the gradient completes at the bottom of the page. Because of this demos mid-page placement, the gradient needs to complete almost a full viewport-height short of the bottom. That would looke like:
background-size: 100% calc(100% - 100vh);
Except the fixed header size factors in a well, so that needs to be subtracted. In the end, the code appears as if it has quite a few magic numbers in it. But they aren’t quite magical as most of them are related to each other genetically. Here’s a fork that turns them all into custom properties so you can see that.
html {
--header-size: 125px;
--scrollbar-height: 3px;
--scrollbar-bg: #ddd;
--scrollbar-progress-color: #0089f2;
}
header {
position: fixed;
top: 0;
height: var(--header-size);
width: 100%;
background: white;
padding: 1rem 2rem;
}
main {
margin-top: calc(var(--header-size) + var(--scrollbar-height));
padding: 2rem;
}
body {
background: linear-gradient(
to right top,
var(--scrollbar-progress-color) 50%,
var(--scrollbar-bg) 50%
);
background-size: 100%
calc(100% - 100vh + var(--header-size) + var(--scrollbar-height) + 1px);
background-repeat: no-repeat;
margin: 0;
font-family: "Open Sans", sans-serif;
}
body::before {
content: "";
position: fixed;
top: calc(var(--header-size) + var(--scrollbar-height));
bottom: 0; width: 100%;
z-index: -1;
background: white;
}
Why do this?
- It’s kinda fun.
- Some browsers don’t have scrollbars at all (think mobile, and “Scroll scrollbars only when scrolling” setting on macOS).
If you want to do something really fancy, like display the percentage of how far you’ve scrolled through the page, or even fancier like showing an estimated reading time that is programmatically calculated, well, that’s all doable, but you’re in JavaScript territory.