Reading progress bar
A thin bar across the top fills as you move through an article.
A table of contents where every chapter has its own small bar showing how far into it you are.
For each chapter the script computes progress from the moment its top reaches the middle of the screen to the moment its bottom does. Finished chapters stay full, upcoming ones stay empty, and the current one fills live.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Chapter progress</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; }
body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; -webkit-font-smoothing: antialiased; }
body { background: #f7f7fb; color: #23233a; display: grid; grid-template-columns: 220px 1fr; }
.toc { position: sticky; top: 0; height: 100vh; padding: 40px 20px; border-right: 1px solid #e2e2ee; }
.toc a { display: block; color: inherit; text-decoration: none; font: 600 .95rem system-ui, sans-serif; margin-bottom: 18px; }
.toc i { display: block; height: 4px; margin-top: 6px; border-radius: 2px; background: #e2e2ee; overflow: hidden; }
.toc i b { display: block; height: 100%; width: 100%; background: #6c5ce7; transform-origin: 0; transform: scaleX(0); }
.doc { padding: 10vh 6vw 60vh; max-width: 640px; }
.doc section { margin-bottom: 10vh; }
.doc h2 { font: 800 2rem system-ui, sans-serif; }
.doc p { font: 1.1rem/1.7 system-ui, sans-serif; color: #55556e; white-space: pre-line; }
@media (max-width: 560px) { body { grid-template-columns: 120px 1fr; } .toc { padding: 24px 10px; } }
</style>
</head>
<body>
<aside class="toc" id="toc"></aside>
<main class="doc" id="doc">
<section><h2>Installation</h2><p></p></section>
<section><h2>Configuration</h2><p></p></section>
<section><h2>Usage</h2><p></p></section>
<section><h2>Theming</h2><p></p></section>
<section><h2>Deployment</h2><p></p></section>
</main>
<script>
const text = 'Every chapter of a long document has its own length.\nA single page bar hides that, so this sidebar shows each chapter separately.\n\nRead on and watch the current chapter fill while finished ones stay full.\n\nThis paragraph is here to give the section some height, the way real documentation would.\nMore text, more scrolling, more progress.\n\nAnd one more line before the next heading.';
const sections = document.querySelectorAll('.doc section');
const toc = document.getElementById('toc');
const bars = [];
sections.forEach((s, i) => {
s.querySelector('p').textContent = text;
s.id = 'c' + i;
const a = document.createElement('a');
a.href = '#c' + i;
a.innerHTML = s.querySelector('h2').textContent + '<i><b></b></i>';
toc.appendChild(a);
bars.push(a.querySelector('b'));
});
function update() {
const mid = innerHeight / 2;
sections.forEach((s, i) => {
const r = s.getBoundingClientRect();
const p = Math.min(1, Math.max(0, (mid - r.top) / r.height));
bars[i].style.transform = 'scaleX(' + p.toFixed(3) + ')';
});
}
addEventListener('scroll', update, { passive: true });
update();
</script>
</body>
</html>