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";23const Progress = () => {4 const [progress, setProgress] = useState(0);56 return <progress />;7};
Note: Remember to import
useStateif 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
useEffectif 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 };56 window.addEventListener("scroll", calculatePercentage);7 return () => {8 window.removeEventListener("scroll", calculatePercentage);9 };10}, []);
Let's step through this code a little.
- We define our
calculatePercentagefunction 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;34 const total = document.documentElement.scrollHeight - document.documentElement.clientHeight;5};
- We've declared a variable called
scrolledwhich references how far we've scrolled on the Y axis within the window. - We've declared a variable called
totalwhich 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;34 const total = document.documentElement.scrollHeight - document.documentElement.clientHeight;56 const percentage = (scrolled / total) * 100;78 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";23const Progress = () => {4 const [progress, setProgress] = useState(0);56 useEffect(() => {7 const calculatePercentage = () => {8 const scrolled = window.scrollY;910 const total = document.documentElement.scrollHeight - document.documentElement.clientHeight;1112 const percentage = (scrolled / total) * 100;1314 setProgress(percentage);15 };1617 window.addEventListener("scroll", calculatePercentage);18 return () => {19 window.removeEventListener("scroll", calculatePercentage);20 };21 }, []);2223 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}1213progress::-webkit-progress-bar {14 background-color: transparent;15}1617progress::-webkit-progress-value {18 background-color: black;19}2021progress::-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.