Create scroll indicator in react

Learn how to create scroll indicator in react.

Scroll indicator is used to indicate how much of a page has been scrolled, so that user gets a good idea about what is the length of the content.

React scroll indicator

To create this component we need few extra packages.

  • classnames: This helps us to use CSS classes as javascript objects, we just need to name our CSS file as filename.module.css

Following is the folder structure of our component.

React scroll indicator folder structure

As you now know what we have to built, so lets dig in and start building it.

Import all the required packages and setup the structure.

import React, { Component } from "react";
import styles from "./index.module.css";

class ScrollIndicator extends Component {
  //Other code will go here
}

export default ScrollIndicator;

We have created a stateful component because we need to maintain the state of scroll position of the component.

To calculate the amount of page scrolled, we have to first listen to the scroll event so that we get the required values for calculation.

Assign the event listener when the component is mounted inside the componentDidMount life cycle method.

componentDidMount() {
  window.addEventListener("scroll", this.calculateScrolledInPercentage);
}

Now we need to calculate the percentage and store the data in state so that component is updated properly after scroll position is changed.

  state = {
    scrolledPercentage: 0
  };

  calculateScrolledInPercentage = () => {
    //How much scrolled
    const winScroll =
      document.body.scrollTop || document.documentElement.scrollTop;

    //Full height
    const height =
      document.documentElement.scrollHeight -
      document.documentElement.clientHeight;

    //Percentage scrolled
    const scrolledPercentage = (winScroll / height) * 100;
    
    //Update state
    this.setState({
      scrolledPercentage
    });
  };

We are calculating percentage scrolled relative to root parent rather than any specific element that is why we are getting the scroll position of body or documentElement which is html tag.

Once we have the scrolled position, let us now render the layout.

render() {
    const { scrolledPercentage } = this.state;

    return (
      <div className={styles.header}>
        <h1>Scroll Indicator</h1>
        <div className={styles.progressContainer}>
          {/* Update the width in percentage */}
          <div
            className={styles.progressBar}
            style={{ width: `${scrolledPercentage}%` }}
          ></div>
        </div>
      </div>
    );
  }

The last thing pending is memory clean up, when the component is about to be removed remove the listener. We can use the componentWillUnmount method.

  componentWillUnmount() {
    window.removeEventListener("scroll", () => {});
  }

Complete code of react scroll indicator

import React, { Component } from "react";
import styles from "./index.module.css";

class ScrollIndicator extends Component {
  state = {
    scrolledPercentage: 0
  };

  calculateScrolledInPercentage = () => {
    //How much scrolled
    const winScroll =
      document.body.scrollTop || document.documentElement.scrollTop;

    //Full height
    const height =
      document.documentElement.scrollHeight -
      document.documentElement.clientHeight;

    //Percentage scrolled
    const scrolledPercentage = (winScroll / height) * 100;

    //Update state
    this.setState({
      scrolledPercentage
    });
  };

  componentDidMount() {
    window.addEventListener("scroll", this.calculateScrolledInPercentage);
  }

  componentWillUnmount() {
    window.removeEventListener("scroll", () => {});
  }

  render() {
    const { scrolledPercentage } = this.state;

    return (
      <div className={styles.header}>
        <h1>Scroll Indicator</h1>
        <div className={styles.progressContainer}>
          {/* Update the width in percentage */}
          <div
            className={styles.progressBar}
            style={{ width: `${scrolledPercentage}%` }}
          ></div>
        </div>
      </div>
    );
  }
}

export default ScrollIndicator;

Complete style code

The styling for this component is extremely simple, we have positioned the indicator div as an absolute inside a relative parent and width of it will be dynamically updated through code.

.header {
  position: fixed;
  top: 0;
  z-index: 1;
  width: 100%;
  background-color: #f44336;
  text-align: center;
  box-shadow: 0 1px 3px #000;
}

/* The progress container (grey background) */
.progressContainer {
  width: 100%;
  height: 8px;
  background: #ccc;
}

/* The progress bar (scroll indicator) */
.progressBar {
  height: 8px;
  background: #4caf50;
  width: 0%;
  transition: width 0.2s linear;
}

Input

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import ScrollIndicator from "./Components/ScrollIndicator";
import * as serviceWorker from "./serviceWorker";

ReactDOM.render(
  <div className="abc">
    <ScrollIndicator />
  </div>,
  document.getElementById("root")
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();