Scroll Indicator using React

Sarah Siqueira - Mar 29 '23 - - Dev Community

A scroll indicator is an essential component in an application design. It is convenient to show the user how much has been scrolled.

Prebuilt scroll components

Often when there is a need to implement a scroll indicator, we can refer to an existing library, plugin, package, or something already built.

The code present in this tutorial itself is available as a npm package for install running npm i scroll-bar-indicator.

This is reasonable and perfectly fine, however in some cases, using external libraries often adds unnecessary dependencies and code bloatware to our application.

This might lead to extra load time because these dependencies have to be downloaded, and can lead to code conflicts between the library or plugin and our existing application codebase, and by the way, this is how dependency hell may start...

That's why know how to build an own scroll indicator can be the most effective solution sometimes. It's a feature we can implement with not much effort in React.

Assuming you already have a React app running, we will create a new component named Scroll Indicator.

// scroll-indicator.js

import { useState, useEffect, useCallback } from 'react';

function ScrollIndicator() {
    const [scroll, setScroll] = useState(0);

    const onScroll = useCallback(() => {
        const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
        const maxHeight = scrollHeight - clientHeight;
        const scrolledPercent = ( scrollTop / maxHeight) * 100;
        setScroll(scrolledPercent);
 }, []);

    useEffect (() => {
        window.addEventListener("scroll", onScroll);
        return () => {
            window.removeEventListener("scroll", onScroll);
 };
 }, [onScroll]);

    const scrollString = Math.trunc(scroll);

    return (
        <div className='scroll_box'>
            <div className="scroll_bar">
                <div style={{ width: `${scroll}%` }} className="scroll_indicator"></div>
                <p className='scroll_info'>{'Text '}{scrollString}{'% scrolled'}</p>
            </div>
        </div>
 )
}

export default ScrollIndicator;
Enter fullscreen mode Exit fullscreen mode

Let's break the code into further steps!

Imports

import { useState, useEffect, useCallback } from 'react';

useState: Allows you to add a state to your functional components.
useEffect: Lets you perform side effects in your components, such as adding or cleaning up event listeners.
useCallback: Returns a memoized version of a callback function, which can help prevent unnecessary re-renders.

Component definition

const ScrollComponent = () => {

This defines a functional component named ScrollComponent.

State Definition

const [scroll, setScroll] = useState(0);

scroll: State variable that holds the current scroll position as a percentage.
setScroll: A function to update the scroll state.
useState(0): Initializes the scroll state to 0.

Scroll Event Handler

  const onScroll = useCallback(() => {
    const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
    const maxHeight = scrollHeight - clientHeight;
    const scrolledPercent = (scrollTop / maxHeight) * 100;
    setScroll(scrolledPercent);
  }, []);
Enter fullscreen mode Exit fullscreen mode

The useCallback hook memoizes the onScroll function, ensuring it does not change between renders unless its dependencies change, improving performance.

Effect Hook for Scroll Event Listener

  useEffect(() => {
    window.addEventListener("scroll", onScroll);
    return () => {
      window.removeEventListener("scroll", onScroll);
    };
  }, [onScroll]);
Enter fullscreen mode Exit fullscreen mode

The useEffect hook ensures that the event listener is added when the component mounts and removed when the component unmounts, preventing memory leaks. The return statement cleans up the event listener when the component unmounts
The dependency array [onScroll] ensures that this effect runs only once when onScroll is first created and any time onScroll changes (which it won't in this case, because it's memoized with an empty dependency array).

Truncate Scroll Percentage

const scrollString = Math.trunc(scroll);

scrollString: Holds the truncated value of scroll (i.e., the decimal part is removed to get an integer value).

Render

Finally we render a div containing the scroll element. The trick here is that, the width for the scroll_indicator element will change using the scrollString value from the React app:

<div style={{ width:${scroll}%}} className="scroll_indicator"></div>

We will render a scroll_box div with a scroll_bar element, both will be always visible. For the scrollIndicator, the width will be dynamically filled as the scroll_info value either.

  return (
        <div className='scroll_box'>
            <div className="scroll_bar">
                <div style={{ width: `${scroll}%` }} className="scroll_indicator"></div>
                <p className='scroll_info'>{'Text '}{scrollString}{'% scrolled'}</p>
            </div>
        </div>
    )
Enter fullscreen mode Exit fullscreen mode

Export Component

export default ScrollComponent;

Exports the ScrollComponent so it can be imported and used in other parts of the application. You can install this same code running npm i scroll-bar-indicator.

Styling

Lastly, but not least important, the CSS.

.scroll_box {
    background: rgb(245, 245, 245);
    height: 20vh;
    border:#b7b7b7;
    border-style: solid;
    border-width: 0.05rem;
    margin-left: 2rem;
    margin-right: 2rem;
}

.scroll_bar {
    background: rgb(179, 179, 179);
    position: relative;
    top: 35% ;
    height: 2rem;
    max-width: 100%;
    margin-left: 2rem;
    margin-right: 2rem;
}

.scroll_indicator {
    background: linear-gradient(to right ,  #048181, #079f9f, #0eb1b1);
    height: 2rem;
}

.scroll_info {
    color:#333;
    font-size: 1rem;
    padding: 1rem;
}
Enter fullscreen mode Exit fullscreen mode

Check the code repository and the deployed version on Vercel, available here. Feel free to install and use in your projects, as also to contribute by adding issues and submit pull requests at the GitHub Project repository.

P.S. That scrollIndicator component is the one you can see working on my portfolio.

. . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player