How to build an article progress indicator in React

Today, we're going to build an article progress indicator, also known as a reading position indicator. These can be useful to allow readers to easily visualise the length of an article, and see their reading progress (more on this later).

Table of contents

What is an article progress indicator?

There are many different implementations but the most common is a slim bar along the top of the page (as you can see above on my site).

Here's what we're going to be building today:

Why use an article progress indicator?

There are a few different uses for an article progress indicator, primarily giving users an easy visualisation of the length of an article, and their reading progress. They are also a cool little feature which doesn't take long to build.

However, the use of such progress indicators is highly contrived. Arguments against include potential confusion with websites such as YouTube that use a similar looking progress bar to indicate loading progress, and the fact that a native scrollbar serves the same purpose as a reading indicator.

Use your own judgement here and decide whether or not to implement one on your site.

Tutorial

Component and state

Let's start by creating a new component called Progress and returning a native progress element.

1const Progress = () => {
2 return <progress />;
3};

Great, now let's add a progress state to our component and initialise it's value to 0.

1import { useState } from "react";
2
3const Progress = () => {
4 const [progress, setProgress] = useState(0);
5
6 return <progress />;
7};

Note: Remember to import useState if you haven't already.

Awesome. The native progress element has two attributes we're going to use in this tutorial.

value which is the current progress value (our progress state value). max which is the maximum progress value (100 in our case as we're using a percentage).

Let's add those to our progress element.

1<progress value={progress} max={100} />

Next we will make our progress indicator work.

Effects

Let's create an effect using useEffect with an empty dependency array which will run when our component mounts.

1useEffect(() => {
2 // ...
3}, []);

Note: Remember to import useEffect if you haven't already.

Now, let's create a function within our effect called calculatePercentage which will calculate our progress percentage.

1useEffect(() => {
2 const calculatePercentage = () => {
3 // ...
4 };
5}, []);

Events

We'll also bind a scroll event to the window, and call our calculatePercentage function which we just defined.

1useEffect(() => {
2 const calculatePercentage = () => {
3 // ...
4 };
5
6 window.addEventListener("scroll", calculatePercentage);
7 return () => {
8 window.removeEventListener("scroll", calculatePercentage);
9 };
10}, []);

Let's step through this code a little.

  • We define our calculatePercentage function within our effect.
  • When our effect runs (on component mount), we bind a scroll event to the window.
  • When our component unmounts, the cleanup function runs (which is what we return from our effect), which removes our event listener.

Great, now let's add the business logic to our calculatePercentage function.

Functionality

1const calculatePercentage = () => {
2 const scrolled = window.scrollY;
3
4 const total = document.documentElement.scrollHeight - document.documentElement.clientHeight;
5};
  • We've declared a variable called scrolled which references how far we've scrolled on the Y axis within the window.
  • We've declared a variable called total which references the total height of the document (scrollHeight) minus the window height (clientHeight).

Now, our total variable is the maximum scrollable distance of our page. This means that we can easily work out our progress percentage by dividing how far we've scrolled by our total scrollable distance and multiplying it by 100.

Finally, we will store this percentage in our progress state.

1const calculatePercentage = () => {
2 const scrolled = window.scrollY;
3
4 const total = document.documentElement.scrollHeight - document.documentElement.clientHeight;
5
6 const percentage = (scrolled / total) * 100;
7
8 setProgress(percentage);
9};

Now you have a functional article progress indicator within your React application. Your final component should look like this:

1import { useEffect, useState } from "react";
2
3const Progress = () => {
4 const [progress, setProgress] = useState(0);
5
6 useEffect(() => {
7 const calculatePercentage = () => {
8 const scrolled = window.scrollY;
9
10 const total = document.documentElement.scrollHeight - document.documentElement.clientHeight;
11
12 const percentage = (scrolled / total) * 100;
13
14 setProgress(percentage);
15 };
16
17 window.addEventListener("scroll", calculatePercentage);
18 return () => {
19 window.removeEventListener("scroll", calculatePercentage);
20 };
21 }, []);
22
23 return <progress value={progress} max={100} />;
24};

Styling

Let's style our component to make it look a little nicer.

1progress {
2 position: fixed;
3 left: 0;
4 top: 0;
5 width: 100%;
6 height: 0.5rem;
7 appearance: none;
8 border: none;
9 background-color: transparent;
10 color: black;
11}
12
13progress::-webkit-progress-bar {
14 background-color: transparent;
15}
16
17progress::-webkit-progress-value {
18 background-color: black;
19}
20
21progress::-moz-progress-bar {
22 background-color: black;
23}

We disable the native progress element styles, and give it our own. Feel free to customise values such as the height and colour to your preference.

Now simply render your component and admire your hard work!

Here's the finished product:

Conclusion

We've learnt how to build an article progress indicator in React, utilising the useState and useEffect hooks.

Hopefully you found this article insightful and you enjoy the fruits of your labour.