You may have noticed those reading progress indicators on top of some pages (mainly news articles and blog posts). They are a bar that grows as you read the article and lets you know how much you have left to read visually. This component is a nice feature, especially on mobile, where the scrollbars are not always visible. But did you know that you can code a reading indicator with just a single HTML element and some CSS? No JavaScript code at all.
In this post, I will explain how to build a reading indicator in CSS. You can check the source code on this CodePen demo:
The Code
First, you will need only one empty HTML element:
<div class="reading-indicator"></div>
This tag goes at the page's root (using absolute positioning to get the document's height), and then we will use the ::before
and ::after
pseudo-elements to "draw" the progress bar.
Note: We could create a more straightforward reading progress indicator without an explicit element, directly in the <body>
with its ::before
and ::after...
but, call me old-fashioned, I prefer not to have "unexpected side-effects," so I like to create an element for it.
The style for the reading-indicator class will be as follows:
.reading-progress {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: calc(100% - 100vh);
z-index: -1;
overflow: hidden;
}
A couple of important things related to this code:
- The height is a funky value; why is that? Why not just 100%? Using 100% would take the page's full size, making the reading indicator stop progressing before reaching 100% (because the 100% would be the exact bottom of the page). We need to consider the height of the view frame (100vh) and refactor it into the element's height.
-
overflow: hidden
would not be required, but as you may have noticed, the indicator edge is diagonal instead of vertical, so after reaching 100%, the whole bar wouldn't take the full width. To fix that, I make the pseudo-elements a bit wider than their container and use the overflow: hidden to avoid unnecessary horizontal scroll bars.
This element is only the container. What draws the reading indicator is the pseudo-elements: with ::before
, we will draw a horizontal bar that occupies the whole width and has a height and sticks to the top of the page:
.reading-progress::before {
content: "";
position: fixed;
top: 0;
left: -0.5%;
width: 101%;
height: 10px;
background: linear-gradient(90deg, #369, #396);
}
But with this, we just have a horizontal line. So how do we make it grow and shrink as the user scrolls around? That's where the trickery begins. We use the ::after
pseudo-element that occupies the whole size of the parent and a diagonal background, one half the color of the background (notice this as it will be a problem later) and the other half transparent:
.reading-progress::after {
content: "";
position: absolute;
top: 0;
left: -0.5%;
width: 101%;
height: 100%;
background: linear-gradient(to bottom left, #fff 50%, #fff0 50%);
}
And one last (but crucial) style change: we need to make the html tag have a relative position, so its height impacts the reading indicator.
html {
position: relative;
}
That's it. That's the trick: the ::after
pseudo-element covers completely the ::before
, but as we scroll on the page and progress on the triangular background, we reveal more and more of the bar (remember it has a fixed position so that one is always visible).
Some Thoughts
This way of creating a reading indicator is not perfect; it has some interesting points and also some drawbacks. Here is a short list of pros and cons:
Pros:
- Simple to code and develop.
- Responsive (relative units FTW!)
- Supported by all browsers.
Cons:
- It requires a flat background.
- Not visually appealing (the end is tilted)
- It only works at the page level
With some tweaking, it may work at the article level. But with the current implementation, it is more of a glorified scrollbar indicator than any other thing. Which doesn't mean it is not practical. As mentioned above, it could be engaging in some scenarios, especially on cell phones or tablets.
I did some unsuccessful tests, but I still think there has to be a different way to do it (maybe using sticky
and background-attachment?)
Top comments (0)