How to make a reading progress bar with React
React
By Xavier Mod on 3rd December, 2020 · 5 min read
TL;DR
A quick tutorial on how to make a simple reading progress bar using React.
Final code at the end of the page!
Reading progress bars have become essential in blog design and development. Users can quicky see how much they've read and know how long until they finish the post. Big players like Medium have popularised the usage of this elements and are actually very simple to use; one React component and we are ready to go!
For this tutorial, I am going to be using React with Hooks and Styled components .
First create a component called
ReadingIndicator.js
.Second, we import all necessary dependencies.
js1import React, { useState, useEffect, useRef } from 'react'2import styled from 'styled-components'3/* Install the browser-monads dependency if4you are using a static site generator like Gatsby.js */5import { window, document, exists } from 'browser-monads';
Besides functionality, the reading indicator works with two divs. The parent div will serve as a relative wrapper while the second one will be the indicator itself, with an absolute position. The width of the
ReadingProgressBar
will be changed dynamically from the component's props.jsx1const ReadingProgressWrapper = styled.div`2 position: relative;3 height: 2px;4 top: 0;5 background: rgba(0, 0, 0, 0.2);6 width: 100%;7`;89const ReadingProgressBar = styled.div`10 width: ${props => props.width};11 background-color: black;12 position: absolute;13 z-index: 1;14 height: 2px;15`;
Let's now create our core component structure:
jsx1const ReadingProgress = () => {2 const [readingProgress, setReadingProgress] = useState(0);34 return (5 <ReadingProgressWrapper>6 <ReadingProgressBar width={readingProgress} />7 </ReadingProgressWrapper>8 );9 };1011 export default ReadingProgress;
We have set up the structure of our reading indicator. Great. Now let's work on the logic! We just need one small function and our main window scroll listener. Let's do the function first:
js1const scrollListener = (target) => {2 var scrollMaxY = window.scrollMaxY || (document.documentElement.scrollHeight - document.documentElement.clientHeight)34 //5 setReadingProgress(`${Math.floor((window.scrollY || window.scrollTop || document.getElementsByTagName("html")[0].scrollTop) / scrollMaxY * 100)}%`);6};
Let's break down what we have here:
var scrollMaxY
takes the max height of the browser's window. Using just the first property could do the trick, but by adding the second one we make it compatible with other browsers as well. Then we are updating our state with an output like this one:
40%
. Essentially, we are getting the current user's scroll position (cross-browser compatible solution), dividing it by the max height of the window and multiplying it by 100 to get a nice percentage! (Quick maths!)We're mostly done! Let's create a scroll listener which will execute our brand new function every time the user scrolls the page.
js1useEffect(() => {2 window.addEventListener("scroll", scrollListener);3 return () => window.removeEventListener("scroll", scrollListener);4});
We've got out function and our listener up and running, so we add them to our component structure and done! This is how the component looks like:
Final code:
jsx1const ReadingProgressWrapper = styled.div`2 position: relative;3 height: 2px;4 top: 0;5 background: rgba(0, 0, 0, 0.2);6 width: 100%;7`;89const ReadingProgressBar = styled.div`10 width: ${props => props.width};11 background-color: black;12 position: absolute;13 z-index: 1;14 height: 2px;15`;1617const ReadingProgress = () => {18 const [readingProgress, setReadingProgress] = useState(0);1920 const scrollListener = (target) => {21 var scrollMaxY = window.scrollMaxY || (document.documentElement.scrollHeight - document.documentElement.clientHeight)2223 setReadingProgress(`${Math.floor((window.scrollY || window.scrollTop || document.getElementsByTagName("html")[0].scrollTop) / scrollMaxY * 100)}%`);2425 console.log(readingProgress)26 };2728 useEffect(() => {29 window.addEventListener("scroll", scrollListener);30 return () => window.removeEventListener("scroll", scrollListener);31 });3233 return (34 <ReadingProgressWrapper>35 <ReadingProgressBar width={readingProgress} />36 </ReadingProgressWrapper>37 );38 };3940 export default ReadingProgress;
If you want to see how it looks like, just look up on the top left of the screen. I have implemented it on my own website!