Progress Bar On Page Scroll Using React and styled-components

Daniel Eze

Let’s re-implement the progress bar on page scroll effect that you may have seen on many popular blogs including this one.

How we’ll do it

To implement this, we would be making use of React, styled-components and the Document onScroll event. So, without further ado, let’s begin.

Installation

As stated, we’ll be using React and styled-components to implement the progress scroll effect. In order to save time, we’ll make use of Create React App to bootstrap a quick React application for us.

So open your terminal and run the following commands:

$ create-react-app app-name
$ cd app-name

Don't have `create-react-app` installed? Then you can use this command $ npx create-react-app app-name

Alright so next up we need to also install styled-components to our newly created project. So while in the project directory, run

$ npm install styled-components -S

Now if you run npm start in our terminal, you should see something like this:

Homepage of CRA boilerplate


Designing the Layout

So, now that our application is setup, let’s start writing some actual code. We’ll begin by building our page layout and writing some CSS to style the layout.

Navigate to your src/App.js file and then delete everything in the file and add the following lines of code:

src/App.js

import React, { Component } from 'react';
import './App.css';

export default class App extends Component {
  render() {
    return (
      <div className="App">
        <header></header>
        <main>
          <h1>Lorem Ipsum</h1>
          <p>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
          </p>
          <p>...more paragraphs of text, enough so that the page gets a scrollbar</p>
        </main>
      </div>
    );
  }
}

Basically what we just did is convert the stateless functional component that create-react-app generated for us to a class-based component. We also added some dummy text content to fill up the page.

Cool now to add some simple styling to the layout. Open the src/App.css and delete everything in it then add the following CSS lines to it.

header {
  background: linear-gradient(to right, #302E24, #28313E);
  height: 60px;
  width: 100%;
  box-shadow: 0 2px 20px 0 rgba(0, 0, 0, .1);
  margin-bottom: 70px;
}

.App p, .App h1 {
  margin: 30px auto;
  width: 800px;
}

Now running npm start , you should then see something similar to this:

Layout design


Design the Progress Bar

Okay so the next thing on our agenda would be to design the progress bar. To implement this, we will be making use of the styled-components library we installed.

let’s create a file named Progress.js in our src folder. Once that is done, add the following lines of code to the file:

src/Progress.js

import styled from 'styled-components';

const  Progress  =  styled.div`
  position:  fixed;
  background:  linear-gradient(
    to right,
    rgba(250, 224, 66, .8) ${props => props.scroll},
    transparent  0);
  width:  100%;
  height:  4px;
  z-index:  3;
`;

export  default  Progress;

Now, let’s head back to our App.js file and make use of the newly created Progress component:

src/App.js

import React, { Component } from 'react';
import './App.css';
import Progress from './Progress';

export default class App extends Component {
  render() {
    return (
      <div className="App">
        <Progress scroll="70%" />
        {/* ... */}
      </div>
    );
  }
}

Alright!, I guess this is a good time to check how we’re doing so far. So let’s run npm start once again and check out our application. If all goes well, you should have something like this:

Progress bar display


Adding the Scroll Logic

Here comes the fun part, where we implement the scroll logic. Before we start writing the actual code, lets outline the steps that are required to achieve this:

  • Listen for the scroll event on the document object
  • Determine the user’s current scroll distance
  • Have a state variable that holds the current scroll percentage
  • Finally, update the progress scroll with the new state percentage

Alright let’s convert the pseudocode to actual code


Listen for the scroll event on the document object

Okay so in our App.js file, lets create a method called listenToScrollEvent that will listen for scroll events on the DOM. This method would look a little like this:

src/App.js

listenToScrollEvent = () => {
  document.addEventListener("scroll", () => {
    requestAnimationFrame(() => {
      // Calculates the scroll distance
      this.calculateScrollDistance();
    });
  });
};

The method simply listens for any scroll event that takes place on the Document object and then invokes requestAnimationFrame, which notifies the browser that an animation is about to take place. The browser then calls the calculateScrollDistance method that was specified in the requestAnimationFrame.

One final step we need to do is call this method whenever the App component has mounted. So we take advantage of one of React’s lifecycle methods, componentDidMount, to call the listenToScrollEvent method.

src/App.js

componentDidMount()  {
  this.listenToScrollEvent();
}

Now let’s take care of defining the calculateScrollDistance method.


Determine the users current scroll distance

To calculate the user’s current scroll distance, we need to do a few things:

  • We need to get how much the user has scrolled
  • We need to get the browser’s height
  • We need to get the document’s height

Getting how much the user has scrolled

To calculate the current user’s scrolled distance, we can make use of the pageYOffset value available on the windows object.

So let’s create a calculateScrollDistance method and add the following line to the method:

src/App.js

calculateScrollDistance = () => {
  const scrollTop = window.pageYOffset;
}

In case of older IE browsers, the following should also work document.body.scrollTop

Getting the browser’s height

Next we need to get the browser’s height and to do that, we can make use of the innerHeight value that can be accessed via the window object.

The browser’s height simply refers to the height of the viewable browser (i.e Chrome or Firefox) area.

So back into our calculateScrollDistance, let’s add the line that gets the browser window height.

src/App.js

calculateScrollDistance = () => {
  const scrollTop = window.pageYOffset;
  const windowHeight = window.innerHeight;
}

In case of older IE browsers, the following should also work document.body.clientHeight

Getting the document height

Now getting the document’s height is tricky and the reason behind this is because various browsers have different ways in which they interpret or calculate the height of a document.

To bypass this, we need to check different properties that various browsers use to get the document height and make use of Math.max() to get the highest value.

So let’s implement this quickly by creating a method called getDocHeight and add the following lines of code:

src/App.js

getDocHeight  =  ()  =>  {
  return Math.max(
    document.body.scrollHeight,  document.documentElement.scrollHeight,
    document.body.offsetHeight,  document.documentElement.offsetHeight,
    document.body.clientHeight,  document.documentElement.clientHeight
  );
}

Then we call the method in our calculateScrollDistance method like so:

src/App.js

calculateScrollDistance = () => {
  const scrollTop = window.pageYOffset;
  const windowHeight = window.innerHeight;
  const docHeight = this.getDocHeight();
}

Now that we have all the values we require, we can calculate the percentage a user has scrolled by dividing the scrollTop from the total available scroll length of the document (docHeight - winHeight) and multiply the result by 100 to get the result in percentage.

So add the following lines into our code:

src/App.js

calculateScrollDistance = () => {
  const scrollTop = window.pageYOffset;
  const windowHeight = window.innerHeight;
  const docHeight = this.getDocHeight();

  const  totalDocScrollLength  =  docHeight  -  winHeight;
  const  scrollPostion  =  Math.floor(scrollTop  /  totalDocScrollLength  *  100)
}

Now that we have scroll position, we need to add a state variable which we can then update with the current scroll position of the user. To do that, we need to create a state object in your src/App.js file and add a scrollPosition and then set the inital state to 0.

src/App.js

// ...
state = {
  scrollPosition: 0
}

Back into our calculateScrollDistance method, we then need to make use of the setState method given by React which will help update the state of our scrollPosition:

src/App.js

calculateScrollDistance = () => {
  // ...
  this.setState({
    scrollPosition,
  });
}

So the final code will look something like this:-

src/App.js

// ...

state = {
  scrollPosition: 0
}

calculateScrollDistance  =  ()  =>  {
  const  scrollTop  =  window.pageYOffset;
  const  winHeight  =  window.innerHeight;
  const  docHeight  =  this.getDocHeight();

  const  totalDocScrollLength = docHeight  -  winHeight;
  const  scrollPostion  =  Math.floor(scrollTop  /  totalDocScrollLength  *  100);

  this.setState({
    scrollPostion,
  });
}

// ...

Update the progress bar with our scrollPosition state

Finally the last thing to do is to simply pass our scrollPosition state to our Progress bar component.

{/* ... */}
<Progress  scroll={ this.state.scrollPostion  +  '%' }  />

So the final complete code would look something like this:-

import React, { Component } from 'react';
import Progress from './Progress';
import './App.css';

export default class App extends Component {
  state = {
    scrollPostion: 0
  }

  listenToScrollEvent = () => {
    document.addEventListener("scroll", () => {
      requestAnimationFrame(() => {
        this.calculateScrollDistance();
      });
    });
  }

  calculateScrollDistance = () => {
    const scrollTop = window.pageYOffset; // how much the user has scrolled by
    const winHeight = window.innerHeight;
    const docHeight = this.getDocHeight();

    const totalDocScrollLength = docHeight - winHeight;
    const scrollPostion = Math.floor(scrollTop / totalDocScrollLength * 100)

    this.setState({
      scrollPostion,
    });
  }

  getDocHeight = () => {
    return Math.max(
      document.body.scrollHeight, document.documentElement.scrollHeight,
      document.body.offsetHeight, document.documentElement.offsetHeight,
      document.body.clientHeight, document.documentElement.clientHeight
    );
  }

  componentDidMount() {
    this.listenToScrollEvent();
  }

  render() {
    return (
      <div className="App">
        <Progress scroll={this.state.scrollPostion + '%'} />
        <header></header>
        <main>
          <h1>Lorem Ipsum</h1>
          <p>
          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
          </p>
          <p>
          ...
          </p>
        </main>
      </div>
    );
  }
}

And with all of this in place, you should now have a working progress bar on page scroll!


That’s All Folks

Whew! 🤗 There you have it folks, we successfully created the progress scroll effect. Hopefully you find it useful!

  Tweet It

🕵 Search Results

🔎 Searching...

Sponsored by #native_company# — Learn More
#native_title# #native_desc#
#native_cta#