[Updated 2024] Pure CSS Implementation
In CSS, traditional animations are defined with the @keyframes rule and played via the animation property. These animations run on a timeline and are unrelated to user scrolling. Starting with Chrome 115, a new CSS property animation-timeline: scroll(); is supported. It lets you bind an animation to scroll events, creating scroll-driven effects so we can build a page progress bar with nothing but CSS.
<style>
.container {
max-width: 800px;
margin: 0 auto;
padding: clamp(1rem, 2vw, 5rem);
}
.progress {
height: 5px;
background: #33a6b8;
border-radius:5px;
position: fixed;
top: 0;
left: 0;
width: 100%;
transform-origin: 0 50%;
animation: scaleProgress auto linear;
animation-timeline: scroll(root);
}
@keyframes scaleProgress {
0% {
transform: scaleX(0);
}
100% {
transform: scaleX(1);
}
}
</style>
<div class="progress"></div>
scrollHeight, scrollTop and clientHeight

Full page image
This is a complete page. The height of the red-boxed area (the visible part of the browser window) is clientHeight. The distance from the top to that red box is scrollTop, and the total height of the page is scrollHeight. With these values we can compute the ratio of the current scroll position to the full page height: scrollTop / (scrollHeight - clientHeight). This ratio tells us what percentage of the page the user has read.
Progress Bar
Here we use MDUI's progress bar, but we need to add position: fixed in the styles so it floats at the top of the page.
Adding the Code
After importing the required files, add a JS snippet and calculate the reading progress as described above:
const TOTAL_HEIGHT = document.documentElement.scrollHeight;
const VIEWPORT_HEIGHT = document.documentElement.clientHeight;
const PROGRESS_BAR_ID = "进度条 ID";
const progressBar = document.getElementById(PROGRESS_BAR_ID);
window.onscroll = function() {
let scrollTop = document.body.scrollTop + document.documentElement.scrollTop;
let scrollPercent = (scrollTop / (TOTAL_HEIGHT - VIEWPORT_HEIGHT)) * 100;
progressBar.style.width = `${scrollPercent}%`;
// Hide the bar if the page is too short
if (TOTAL_HEIGHT <= VIEWPORT_HEIGHT) {
progressBar.style.display = 'none';
} else {
progressBar.style.display = 'block';
}
}
window.addEventListener('scroll', (event) => {
requestAnimationFrame(() => {
let scrollTop = document.body.scrollTop + document.documentElement.scrollTop;
let scrollPercent = (scrollTop / (TOTAL_HEIGHT - VIEWPORT_HEIGHT)) * 100;
progressBar.style.width = `${scrollPercent}%`;
});
});
That's it—you now have a reading progress bar.