Scroll Progress Bar

A fixed top-of-page progress bar animated with GSAP ScrollTrigger scrubbed to the full document scroll. Includes an optional click-to-scroll feature using ScrollToPlugin that jumps to the clicked position on the bar.

gsapscrolltriggerscrollprogressbar

Setup — External Scripts

Setup: External Scripts
html
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/ScrollTrigger.min.js"></script>

<!-- Optional if you want to have the 'click to scroll to' functionality-->
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/ScrollToPlugin.min.js"></script>

Code

index.html
html
<div class="progress-bar-wrap">
  <div class="progress-bar"></div>
</div>
styles.css
css
.progress-bar-wrap {
  z-index: 10;
  cursor: pointer;
  width: 100%;
  height: 1.5rem;
  transition: background-color .2s;
  position: fixed;
  inset: 0% 0% auto;
}

.progress-bar-wrap:hover {
  background-color: #0000000d;
}

.progress-bar {
  transform-origin: 0%;
  transform-style: preserve-3d;
  background-color: #ff4c24;
  width: 100%;
  height: 100%;
  transform: scale3d(0, 1, 1);
}
script.js
javascript
gsap.registerPlugin(ScrollTrigger, ScrollToPlugin);

function initScrollProgressBar() {

  const progressBar = document.querySelector('.progress-bar');
  const progressBarWrap = document.querySelector('.progress-bar-wrap');

  // Animate the progress bar as you scroll
  gsap.to(progressBar, {
    scaleX: 1,
    ease: 'none', // no ease, we control smoothness with the 'scrub' property
    scrollTrigger: {
      trigger: document.body, // Track the entire page
      start: 'top top',
      end: 'bottom bottom',
      scrub: 0.5, // control the amount of time it takes for the bar to catch up with scroll position
    },
  });

  // Click listener to scroll to a specific position, feel free to remove if you dont want it!
  progressBarWrap.addEventListener('click', (event) => {
    const clickX = event.clientX;
    const progress = clickX / progressBarWrap.offsetWidth;
    const scrollPosition = progress * (document.body.scrollHeight - window.innerHeight);

    gsap.to(window, {
      scrollTo: scrollPosition,
      duration: 0.725,
      ease: 'power3.out',
    });
  });
}

// Initialize Scroll Progress Bar
document.addEventListener('DOMContentLoaded', () => {
  initScrollProgressBar();
});

Guide

Overview

The inner .progress-bar starts at scaleX(0) and is tweened to scaleX(1) via a ScrollTrigger that spans the full document body. transform-origin: 0% makes it grow from left to right.

Scrub

scrub: 0.5 makes the bar lag slightly behind the scroll position for a smooth feel. Set to true for an instant lock or increase the value for more drag.

Click to Scroll

The click listener on .progress-bar-wrap calculates what percentage along the bar was clicked, converts it to a scroll position, and animates the window there with ScrollToPlugin. Remove the listener and the ScrollToPlugin script tag if you don't need this feature.