go backBack to blog

Scroll Tracker/Indicator with React and TypeScript

Published on Mar 31 2022

Last updated on Apr 05 2022

Image by Mae Mu
No translation available.
Add translation

A scroll indicator helps users know how far down the page they have scrolled. It is also a fun way to decorate your website or blog. Although it might look hard, creating one will be as easy as pie if you follow this tutorial.

🧐 Also, did you notice something

✨ Yes, my blog post has this! You can see how the scroll indicator is in action as you read through the blog post! 🤩

Getting Started

As indicated in the title, here I am using Create React App with TypeScript framework. In your App.tsx, you should start with something like this:

Code Starter

To better demonstrate the animation, I added about 10 lorem ipsum paragraphs to increase the height of the document and some extra stylings. You can expect the general structure of my `App.tsx` to look like this:

code-snapshot

And the CSS Rules I use are:

code-snapshot

At this point, you will have something like this:

Picture of document

Step 1: Create you Navigation Bar component

A general good rule-of-thumb is when creating a React component, you should create a new folder, which includes the component file and the component's style file.

Here, I will create a NavBar component, it should look somewhat like this:

NavBar.tsx

1
import React from "react";
2
3
interface Props {
4
5
}
6
7
const NavBar: React.FC<Props> = (props) => {
8
return (
9
<nav className="NavBar">
10
<span className="NavBar__Logo">Alissa Nguyen</span>
11
<ul className="NavBar__Links">
12
<li className="NavBar__Link">Home</li>
13
<li>About</li>
14
<li>Contact</li>
15
</ul>
16
</nav>
17
);
18
};
19
20
export default NavBar;
21

You can add the below styling to make it looks better:

1
.NavBar {
2
display: flex;
3
justify-content: space-between;
4
align-items: center;
5
padding: 10px 20px;
6
border-bottom: solid 1px gray;
7
}
8
9
.NavBar__Logo {
10
display: flex;
11
font-size: 2rem;
12
font-weight: 300;
13
margin-right: 2rem;
14
width: 20rem;
15
}
16
17
.NavBar__Links {
18
display: flex;
19
gap: 5rem;
20
justify-content: flex-end;
21
align-items: center;
22
list-style: none;
23
}
24

Then, add the NavBar component into your App.tsx:

Code Snapshot 2

Now to make the navigation bar sticks to the top of the window at all time, you add `position: sticky` to the CSS rules for NavBar.

styles.css

1
.NavBar {
2
display: flex;
3
justify-content: space-between;
4
align-items: center;
5
padding: 10px 20px;
6
border-bottom: solid 1px gray;
7
position: fixed;
8
top: 0;
9
left: 0;
10
right: 0;
11
z-index: 2;
12
background-color: rgb(224, 224, 224);
13
}
14

You now have a navigation bar component that will sticks to the head of the window no matter how far you scroll down the page.

Navbar picture after added stylings

Step 2: Create the Scroll Indicator component

Creating the scroll indicator component gives you more flexibility to customize its stylings. Below is how your ScrollIndicator.tsx should look like.

code-snapshot

Then, attach the component to the bottom of the navigation bar with the below stylings:

styles.css

.NavBar__ScrollIndicator {
background: linear-gradient(
217deg,
rgb(255, 230, 0) 20%,
#dd31ffcc 75%,
rgb(255, 0, 0) 100%
);
bottom: 0;
left: 0;
height: 5px;
position: absolute;
transition: 0.5s cubic-bezier(0.075, 0.82, 0.165, 1);
}

Notice that here the ScrollIndicator has a CSS rule of `position: absolute`, this will position the indicator relatively to the closest parent that has a `position: relative`, `position: fixed`, or `position: sticky` value. Since in step 1, our NavBar component has the rule `position: sticky` and is the closest parent to our indicator component, our ScrollIndicator will positioned with reference to our NavBar. We specify the final position of your indicator with values of toprightbottom, and left.

Right now, you won't see anything on your screen yet, since the width of the scroll indicator is 0. Our next step is to create a trigger function to change the width of the scroll indicator.

Step 3: Create a function to update the scrolled amount to our progress indicator

If you aren't too familiar with React useState hook, this is a perfect opportunity to practice. Tracking the progress of your scroll indicator inside a state ensures instant update without affecting other elements on your website.

ProgressIndicator.tsx

1
import React from "react";
2
3
interface Props {}
4
5
const ProgressIndicator: React.FC<Props> = (props) => {
6
const [progress, setProgress] = React.useState<number>(0);
7
8
return (
9
<div
10
className="NavBar__ScrollIndicator"
11
style={{
12
width: `${progress}%`
13
}}
14
></div>
15
);
16
};
17
18
export default ProgressIndicator;
19

To create the function that will update the scroll indicator of how much of the page the user has scrolled, we want to understand the steps to calculate the amount scrolled:

  1. We find the height of the device's window and the height of the entire document.

  2. We find how much the user has scrolled.

  3. We calculate the percentage of the document that has been scrolled.

  4. We update our `progress` with the percentage calculated.

Below is the complete function:

ProgressIndicator.tsx

function updateProgressIndicator() {
const windowHeight: number = window.innerHeight;
const documentFullHeight: number = document.body.clientHeight;
const scrolled: number = window.scrollY;
const percentageScrolled: number =
(scrolled / (documentFullHeight - windowHeight)) * 100;
setProgress(percentageScrolled);
}

Step 4: Trigger the function to animate the scroll indicator when user scrolls

In this step, we will be using React useEffect hook, I suggest you read more about it if you are not familiar with this hook.

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

Then, add our useEffect hook to our ProgressIndicator component and we will have a complete scroll indicator component as below:

ProgressIndicator.tsx

1
import React from "react";
2
3
interface Props {}
4
5
const ProgressIndicator: React.FC<Props> = (props) => {
6
const [progress, setProgress] = React.useState<number>(0);
7
8
function updateProgressIndicator() {
9
const windowHeight: number = window.innerHeight;
10
const documentFullHeight: number = document.body.clientHeight;
11
12
const scrolled: number = window.scrollY;
13
14
const percentageScrolled: number =
15
(scrolled / (documentFullHeight - windowHeight)) * 100;
16
17
setProgress(percentageScrolled);
18
}
19
20
React.useEffect(() => {
21
updateProgressIndicator();
22
window.addEventListener("scroll", updateProgressIndicator);
23
24
return () => {
25
window.removeEventListener("scroll", updateProgressIndicator);
26
};
27
}, []);
28
29
return (
30
<div
31
className="NavBar__ScrollIndicator"
32
style={{
33
width: `${progress}%`
34
}}
35
></div>
36
);
37
};
38
39
export default ProgressIndicator;
40

So how does this works?

Fundamentally, by using useEffect hook, you want React to do something to your component (our ScrollIndicator component) after it has been rendered. React will save the "effect" (which is the callback function) and call it after our component mounted (added in the DOM). In our useEffect, the callback function does 3 things:

  • It initially calls our function wrote in step 3. This will handle cases where the user refresh the page after they've scrolled halfway down the page. In those cases, they haven't scrolled yet but since we call our function, they will still see the progress indicator (assuming your web application remembers the user scroll position after refresh).

  • It will attempt to add an EventListener to our window, which will call our updateProgressIndicator() function every time the user scrolls.

  • It will return a function that will remove the EventListener above from the window. When the callback function in a useEffect hook returns a function, it means React will call that function (returned from the callback) every time our component unmounted (removed from the DOM). Without doing this, the EventListener won't be removed, and so the updateProgressIndicator() function will be called every time the user scrolls even when our ScrollIndicator does not exist in the DOM.

Hey, you're finished!

That's it for today's tutorial, you can see a complete demonstration below:

Tags:
front-end
tutorials
My portrait picture

Written by Alissa Nguyen

Alissa Nguyen is a software engineer with main focus is on building better software with latest technologies and frameworks such as Remix, React, and TailwindCSS. She is currently working on some side projects, exploring her hobbies, and living with her two kitties.

Learn more about me


If you found this article helpful.

You will love these ones as well.

  • Photo by Steve Johnson on Unsplash
    Apr 06 2023 — 5 min readCSS Architecture: The Fundamentals
    #css#front-end
  • Photo by Nathan da Silva on Unsplash
    Sep 07 2022 — 5 min readIntroduction to HTML
    #front-end#html
  • Photo by Manja Vitolic
    Mar 27 2022 — 5 min readSetting up your custom domain with Namecheap and Netlify
    #front-end

  • Built and designed by Alissa Nguyen a.k.a Tam Nguyen.

    Copyright © 2024 All Rights Reserved.