Scroll Progress Bar with HTML, CSS & JavaScript
20 DAYS 20 PROJECT CHALLENGE
Day #16
Project Overview
Scroll Progress Bar is a simple and beginner-friendly project built using HTML, CSS, and JavaScript. It displays a progress indicator at the top of the webpage that changes as the user scrolls through the content and the progress bar visually represents how much of the page has been viewed. As the user scrolls down, the bar gradually increases in width, and when the user reaches the bottom of the page, the progress reaches 100%.
This project is useful for beginners who want to understand how JavaScript can detect scrolling activity and use page measurements to create dynamic visual effects. It also provides practice with scroll events, DOM manipulation, percentage calculations, CSS positioning, and responsive web design.
Key Features
- Real-Time Progress Bar: The progress bar changes its width constantly during the scrolling process, reflecting the percentage of the web page viewed by the user.
- Progress Bar Placement: It is fixed at the top of the web page, indicating the reader’s progress while scrolling down the page.
- Visual Reading Progress Indicator: A simple script calculates the current position of scrolling and updates the width of the progress bar.
- Percentage-Based Progress Bar: The script calculates the percentage of the page viewed by the user based on the ratio between the scrolled portion and the scrollable height of the window.
- Scroll Event Listener: The scroll event listener updates the progress bar automatically as soon as the user scrolls down the web page. Thus, the bar does not need extra modules to update the progress.
- Scroll Animation and Styling: Using cascading style sheets (CSS), developers can make the bars responsive and animate their changes.
- Simple and Clean Design: The project focuses on one primary feature, making the interface easy to understand without unnecessary elements.
- Easy Customization: Developers can easily change the height, colors, position, and animation of the progress bar to match different website designs.
What You'll Learn
- Understand how JavaScript detects page scrolling.
- Learn how to use scroll event listeners.
- Practice accessing the current scroll position.
- Know how to calculate the total scrollable height of a webpage.
- Learn how percentages can represent scroll progress.
- Practice updating CSS styles dynamically using JavaScript.
- Understand how the
style.widthproperty changes an element’s width. - Learn how fixed positioning keeps an element visible during scrolling.
- Practice combining JavaScript calculations with CSS styling.
HTML Code
HTML creates the basic structure required for the Scroll Progress Bar. A small container is placed near the top of the webpage to hold the progress indicator. Inside this container, another element represents the actual progress bar whose width changes as the user scrolls and the remaining HTML content provides enough page content to make the webpage scrollable. This allows users to move down the page and see the progress indicator update based on their current position.
The CSS file is linked inside the <head> section so the progress bar and webpage content can be styled. Finally, the script.js file is linked near the bottom of the <body>, allowing JavaScript to access the progress bar element and update its width when the user scrolls.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Day 16 — Scroll Progress Bar</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<!-- Progress bar (kept outside main content so it's always at top) -->
<div id="scrollProgress" class="scroll-progress" role="progressbar"
aria-label="Page scroll progress" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
<div class="scroll-progress__bar" id="scrollProgressBar"></div>
</div>
<main class="page">
<header class="site-header">
<h1>Day 16: Scroll Progress Bar</h1>
<p class="lead">A tiny, performant progress indicator that follows the user's scroll position.</p>
</header>
<article class="content">
<!-- Example long content: copy / paste your own content here -->
<section>
<h2>Introduction</h2>
<p>Scroll down to see the progress bar fill as you move through the page. The progress is calculated as the percent of content scrolled and updated using requestAnimationFrame for smooth animations.</p>
</section>
<!-- repeat filler content to make page long enough to scroll -->
<section>
<h2>Section 1</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer ut ...</p>
<p>(Repeat multiple paragraphs for demo.)</p>
</section>
<section>
<h2>Section 2</h2>
<p>Donec mollis, arcu a convallis efficitur, tortor elit pharetra arcu, quis ...</p>
</section>
<section>
<h2>Section 3</h2>
<p>More content... (Use real article or documentation content in production.)</p>
</section>
<!-- add more content to make the demo scrollable -->
<div style="height:500px;"></div>
<footer style="padding:40px 0 80px; color: #9aa4b2;">
<p>End of demo content.</p>
</footer>
</article>
</main>
<script src="script.js"></script>
</body>
</html>
CSS Code
CSS controls the appearance and position of the Scroll Progress Bar. The progress container is typically positioned at the top of the page using fixed positioning so that it remains visible while the user scrolls and the progress bar itself starts with a width of 0%. JavaScript gradually changes this width based on the user’s scroll position. CSS controls the height, background color, and other visual properties of the bar.
The rest of the CSS styles the webpage content by adding appropriate spacing, typography, and layout. This creates a scrollable page where the progress feature can be tested properly. While the fixed layout ensures that the progress indicator stays at the top of the screen on different devices, while responsive styling helps the webpage remain usable on smaller screens.
:root {
--progress-height: 6px;
--progress-bg: rgba(255, 255, 255, 0.04);
--progress-color: linear-gradient(90deg, #7c3aed, #06b6d4);
/* gradient fill */
--progress-transition: width 160ms linear;
--hide-offset: 8px;
/* distance from top to hide when at very top */
}
/* page reset & layout */
* {
box-sizing: border-box
}
html,
body {
height: 100%;
margin: 0;
font-family: Inter, system-ui, -apple-system, 'Segoe UI', Roboto, Arial;
background: #002252;
color: #e6eef6;
-webkit-font-smoothing: antialiased;
}
.page {
max-width: 920px;
margin: 48px auto;
padding: 0 20px;
}
/* progress container - fixed at top */
.scroll-progress {
position: fixed;
top: 0;
left: 0;
right: 0;
height: var(--progress-height);
background: var(--progress-bg);
z-index: 9999;
display: block;
pointer-events: none;
/* avoid intercepting clicks */
transition: transform 280ms ease, opacity 220ms ease;
transform: translateY(0);
opacity: 1;
}
/* hidden state (when at very top) */
.scroll-progress.hidden {
transform: translateY(calc(-1 * (var(--progress-height) + 4px)));
opacity: 0;
}
/* inner fill bar */
.scroll-progress__bar {
height: 100%;
width: 0%;
background: var(--progress-color);
background-clip: padding-box;
/* allow color to be gradient or single color */
transition: var(--progress-transition);
will-change: width;
}
/* header + content styling for demo */
.site-header {
padding: 32px 0 12px;
}
.site-header h1 {
margin: 0 0 6px;
font-size: 22px;
}
.lead {
margin: 0 0 6px;
color: #9aa4b2;
}
/* article content styling */
.content {
line-height: 1.7;
color: #dfe9f5;
}
.content h2 {
margin-top: 28px;
font-size: 18px;
color: #e6eef6;
}
.content p {
margin: 12px 0;
color: #cbd8e6
}
/* small screens tweak */
@media (max-width:520px) {
:root {
--progress-height: 5px;
}
.page {
margin: 28px auto;
padding: 0 14px;
}
} Javascript Code
JavaScript handles the main functionality of the Scroll Progress Bar. First, it selects the progress bar element from the HTML using its ID or class. A scroll event listener is then added to the webpage. Whenever the user scrolls, JavaScript calculates the current vertical scroll position and compares it with the total scrollable height of the document and the progress percentage is calculated by dividing the current scroll position by the available scrollable distance and multiplying the result by 100. JavaScript then applies this percentage to the progress bar’s width style.
For example, if the user has scrolled halfway through the page, the progress bar width becomes approximately 50%. When the user reaches the bottom of the webpage, the bar reaches 100%, providing a clear visual indication that the page has been completely viewed.
// Day 16 — Scroll Progress Bar
// Uses requestAnimationFrame for smooth updates and avoids heavy work on each scroll event.
// DOM elements
const progress = document.getElementById('scrollProgress');
const progressBar = document.getElementById('scrollProgressBar');
// Config: auto-hide when at top
const AUTO_HIDE_AT_TOP = true; // set false to always show bar
// internal state
let ticking = false; // rAF flag
function calculateScrollPercent() {
const doc = document.documentElement;
const body = document.body;
// Total scrollable height = document height - viewport height
const scrollTop = window.pageYOffset || doc.scrollTop || body.scrollTop || 0;
const scrollHeight = Math.max(
doc.scrollHeight || 0,
body.scrollHeight || 0
);
const clientHeight = doc.clientHeight || window.innerHeight || 0;
const scrollable = Math.max(0, scrollHeight - clientHeight);
// avoid division by zero on short pages
const fraction = scrollable === 0 ? 0 : (scrollTop / scrollable);
const percent = Math.min(100, Math.max(0, fraction * 100));
return { percent, scrollTop, scrollable };
}
function updateProgress() {
ticking = false;
const { percent, scrollTop } = calculateScrollPercent();
// update bar width and aria attribute
progressBar.style.width = percent + '%';
progress.setAttribute('aria-valuenow', Math.round(percent));
// auto-hide logic (optional): hide when at top (no scroll)
if (AUTO_HIDE_AT_TOP) {
if (scrollTop <= 2) {
progress.classList.add('hidden');
} else {
progress.classList.remove('hidden');
}
}
}
// Scroll event handler — minimal work; schedule rAF if not already scheduled
function onScroll() {
if (!ticking) {
window.requestAnimationFrame(updateProgress);
ticking = true;
}
}
// Also update on resize since viewport height changes scrollable area
function onResize() {
if (!ticking) {
window.requestAnimationFrame(updateProgress);
ticking = true;
}
}
// initialize
function init() {
// initial update (in case page loads scrolled or refreshed)
updateProgress();
// attach listeners
window.addEventListener('scroll', onScroll, { passive: true });
window.addEventListener('resize', onResize);
// also update when content loads images or fonts change layout
window.addEventListener('DOMContentLoaded', updateProgress);
window.addEventListener('load', updateProgress);
}
init();
Related Projects
Day 14 : Image Gallery with Modal
Displays images in a grid with a pop-up modal view.
Concepts: DOM traversal, event bubbling.
Day 18 : Text-to-Speech Converter
Converts entered text into speech.
Concepts: Web Speech API.
Day 19 : Product Filter List
Filter products or items by search or category in real-time.
Concepts: Array filter(), DOM rendering.