JavaScript Snacks 002: Building a Progress Bar for Scrolling

March 4, 2024

Back to Articles

Have you ever read a blog post that had a progress bar at the top, making it easy to keep track of how much more there is to go? Let's implement that in the simplest way possible.

As always, start by creating a new index.html with a linked style.css and index.js file. The bar will be contained by a <div> that sticks to the top of the page at all times. The effect of filling the bar will be handled by a <span> that grows and shrinks in width.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>002 Progress Bar</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div id="progressBar">
    <span id="progressFill"></span>
  </div>
  <script src="index.js"></script>
</body>
</html>

Now, let's head over to our styles before we add any JavaScript.

body {
  margin: 0;
  width: 100%;
  height: 10000px;
}

#progressBar {
  position: sticky;
  width: 100%;
  height: 10px;
  top: 0px;
}

#progressFill {
  display: block;
  width: 0px;
  background-color: green;
  height: 100%;
}

This should take care of sticking the bar to the top of the page. Notice how we have set the body to be 10,000 pixels in height. This will force the browser to allow scrolling. Also, notice the progressBar has its position set to sticky and its top set to 0px. These two attributes allow it to hold tight to the top, even when scrolling down.

Our progressFill element won't be noticeable yet. If you inspect the page and bump up its width, you'll see it start to grow.

Then, in index.js, let's select some of the elements we need and add some simple math to adjust the width of progressFill on scroll.

const body = document.querySelector('body');
const progressFill = document.getElementById('progressFill');


function updateProgress() {
  /*  
    math will go here
  */
}

document.addEventListener('scroll', updateProgress);

So far, what we've done is added an event listener on the document that will fire for every scroll event. For each pixel scrolled, we need to add or remove width from the progressFill element. Its width will be equal to the percentage that's been scrolled.

But how to we get that percentage?

The percentage scrolled is equal to the number of pixels scrolled so far divided by how much there is to go. How much there is to go is the total body height minus what can be seen within the browser.

percentage = scrolled / (bodyHeight - windowHeight)

Then, we multiply by 100 to format it like a percentage along with a '%' at the end.

To calculate scrolled, we can access window.scrollY. Then, we get the body height from body.offsetHight, and the window height from window.innerHeight. Our function now looks like this.

function updateProgress() {
  const scrolled = window.scrollY;
  const bodyHeight = body.offsetHeight;
  const windowHeight = window.innerHeight;
  const percentage = (scrolled / (bodyHeight - windowHeight)) * 100;
  
  progressFill.style.width = `${percentage}%`;
}

Notice we are setting the width of the progressFill element at the end. You're progress bar should work at this point! Here is what your index.js should look like at this point.

const body = document.querySelector('body');
const progressFill = document.getElementById('progressFill');

function updateProgress() {
  const scrolled = window.scrollY;
  const bodyHeight = body.offsetHeight;
  const windowHeight = window.innerHeight;
  const percentage = (scrolled / (bodyHeight - windowHeight)) * 100;
  
  progressFill.style.width = `${percentage}%`;
}

document.addEventListener('scroll', updateProgress);

Now for a little extra, we'll add a display to see the current percentage.

First, add another <div> to the body with the id of percentLabel.

<body>
  <div id="progressBar">
    <span id="progressFill"></span>
  </div>
  <div id="percentLabel"></div>
  <script src="index.js"></script>
</body>

Next, add minimal style.

#percentLabel {
  position: fixed;
  font-size: 10rem;
}

Finally, add more JavaScript logic. We will select the element, then add one more line to our old function that calls a new function. We will also call the function at the end so that the percentage shows immediately when the page loads. Here is the updated file.

const body = document.querySelector('body');
const progressFill = document.getElementById('progressFill');
const percentLabel = document.getElementById('percentLabel');

function updateProgress() {
  const scrolled = window.scrollY;
  const bodyHeight = body.offsetHeight;
  const windowHeight = window.innerHeight;
  const percentage = (scrolled / (bodyHeight - windowHeight)) * 100;
  
  progressFill.style.width = `${percentage}%`;
  displayPercentage(percentage)
}

function displayPercentage(p) {
  percentLabel.textContent = `${Math.floor(p)}%`;
}

document.addEventListener('scroll', updateProgress);

updateProgress();

To see this JavaScript snack in action, you see it on CodePen or jsFiddle,

You can also see the source code on GitHub.

Happy Snacking! -🥑

Back to Articles