Scroll Progress & Section Indicator スクロール進行バー&セクションインジケーター
Header reading progress bar synced with active section tracking and scroll completion percentage. スクロール量と現在閲覧中のセクションをヘッダーバーと連動させるアクセシブルな読了進行インジケーター。
ライブデモ Live Demo
2 アクセシビリティと配慮2. Accessibility Details
3 カスタマイズと展開3. Customization & Layouts
4 まとめと実装のポイント4. Summary & Takeaways
概要・用途・特徴Overview, Usage & Features
何ができるかWhat it does
A sticky header reading progress bar paired with real-time percentage updates and dynamic active section tracking.
スクロール領域内の現在位置から読了率(0〜100%)を算出し、ヘッダー直下のプログレスバーと現在セクションバッジを連動更新するUIです。
どこで使うかWhere to use
Used in long documentation, editorial content, tutorials, and modal scroll areas to improve orientation.
長文ドキュメント、技術ブログ、利用規約同意モーダルなど、全体の長さを把握させたい領域で幅広く活用できます。
特徴Key features
Smooth width transitions, active section detection, percentage readout, CSS variable theme integration, accessible structure.
滑らかな幅の補間、現在位置に応じたセクションのハイライト検知、パーセンテージ表示、CSS変数によるテーマ変更対応。
調整可能パラメータ Adjustable Parameters
| Parameter | Default | Description |
|---|---|---|
bar-color | #38bdf8 | Progress bar fill and glow color |
bar-height | 4px | Thickness of the sticky progress line |
実装コード Implementation Code
<div class="m020-stage">
<div class="m020-header">
<div class="m020-title">Reading Progress <span class="m020-badge" id="m020-badge">Sec 1</span></div>
<div class="m020-pct" id="m020-pct">0%</div>
<div class="m020-bar-track"><div class="m020-bar-fill" id="m020-fill"></div></div>
</div>
<div class="m020-scroll-area" id="m020-area">
<article class="m020-section is-active"><h4><span class="m020-sec-num">1</span> Design</h4><p>Content...</p></article>
<article class="m020-section"><h4><span class="m020-sec-num">2</span> Accessibility</h4><p>Content...</p></article>
</div>
</div>
.m020-stage {
position: relative;
width: 100%;
max-width: 640px;
border-radius: 16px;
background: #090d16;
border: 1px solid rgba(148, 163, 184, 0.2);
overflow: hidden;
}
.m020-header {
position: sticky;
top: 0;
z-index: 10;
background: rgba(15, 23, 42, 0.88);
backdrop-filter: blur(8px);
padding: 12px 18px;
display: flex;
justify-content: space-between;
}
.m020-bar-track {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: var(--m020-bar-height, 4px);
background: rgba(148, 163, 184, 0.12);
}
.m020-bar-fill {
height: 100%;
background: linear-gradient(90deg, var(--m020-bar-color, #38bdf8), #818cf8);
box-shadow: 0 0 10px var(--m020-bar-color, #38bdf8);
transition: width 0.08s ease-out;
}
.m020-scroll-area {
height: 280px;
overflow-y: auto;
padding: 20px 24px;
}
.m020-section {
padding: 20px 0;
opacity: 0.35;
transform: translateY(12px);
transition: opacity 0.35s ease, transform 0.35s ease;
}
.m020-section.is-active {
opacity: 1;
transform: translateY(0);
}
import React, { useState, useRef } from 'react';
import './M-020.css';
export default function ScrollProgressReveal({
sections = [],
barColor = '#38bdf8',
barHeight = 4
}) {
const [progress, setProgress] = useState(0);
const [activeSec, setActiveSec] = useState(1);
const scrollRef = useRef(null);
const handleScroll = () => {
const el = scrollRef.current;
if (!el) return;
const max = el.scrollHeight - el.clientHeight;
const pct = max > 0 ? Math.round((el.scrollTop / max) * 100) : 0;
setProgress(pct);
const children = el.querySelectorAll('.m020-section');
children.forEach((sec, idx) => {
if (sec.offsetTop - el.scrollTop <= 120) {
setActiveSec(idx + 1);
}
});
};
return (
<div className="m020-stage" style={{ '--m020-bar-color': barColor, '--m020-bar-height': `${barHeight}px` }}>
<div className="m020-header">
<span>Reading Progress <span className="m020-badge">Sec {activeSec}</span></span>
<span className="m020-pct">{progress}%</span>
<div className="m020-bar-track">
<div className="m020-bar-fill" style={{ width: `${progress}%` }} />
</div>
</div>
<div className="m020-scroll-area" ref={scrollRef} onScroll={handleScroll}>
{sections.map((sec, idx) => (
<article key={idx} className={`m020-section ${activeSec === idx + 1 ? 'is-active' : ''}`}>
<h4><span className="m020-sec-num">{idx + 1}</span> {sec.title}</h4>
<p>{sec.content}</p>
</article>
))}
</div>
</div>
);
}
.m020-stage {
position: relative;
width: 100%;
max-width: 640px;
border-radius: 16px;
background: #090d16;
border: 1px solid rgba(148, 163, 184, 0.2);
overflow: hidden;
}
.m020-header {
position: sticky;
top: 0;
z-index: 10;
background: rgba(15, 23, 42, 0.88);
backdrop-filter: blur(8px);
padding: 12px 18px;
display: flex;
justify-content: space-between;
}
.m020-bar-track {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: var(--m020-bar-height, 4px);
background: rgba(148, 163, 184, 0.12);
}
.m020-bar-fill {
height: 100%;
background: linear-gradient(90deg, var(--m020-bar-color, #38bdf8), #818cf8);
box-shadow: 0 0 10px var(--m020-bar-color, #38bdf8);
transition: width 0.08s ease-out;
}
.m020-scroll-area {
height: 280px;
overflow-y: auto;
padding: 20px 24px;
}
.m020-section {
padding: 20px 0;
opacity: 0.35;
transform: translateY(12px);
transition: opacity 0.35s ease, transform 0.35s ease;
}
.m020-section.is-active {
opacity: 1;
transform: translateY(0);
}
仕組みとカスタマイズHow It Works & Customization
仕組みHow it works
Listens to scroll events on the target element, calculates the ratio of scrollTop to scrollable distance, and toggles active classes based on section offsets.
スクロールイベントからscrollTopと最大スクロール量の比率を算出してバー幅に反映し、セクションのoffsetTopとの比較でアクティブクラスを切り替えます。
カスタマイズ方法Customization
Change bar color, height, and background gradients using custom properties --m020-bar-color and --m020-bar-height.
CSSカスタムプロパティ(--m020-bar-color, --m020-bar-height)でバーの配色や高さを自由にカスタマイズ可能です。
注意点Caveats
When binding to the window object on mobile devices, account for dynamic URL bar resizing in height calculations.
モバイルブラウザのウィンドウスクロールに適用する際は、アドレスバーの開閉による高さ変動(dvh / vh)にご注意ください。
よくある質問 Frequently Asked Questions
How can I adapt this to the full browser window instead of a container? コンテナ内ではなくブラウザウィンドウ全体のスクロールに適用するにはどうすればよいですか?
Listen to window.addEventListener('scroll') and compute progress with document.documentElement.scrollTop / (document.documentElement.scrollHeight - window.innerHeight).
windowのscrollイベントを監視し、document.documentElement.scrollTop / (scrollHeight - window.innerHeight) で全体の進捗率を計算して適用します。
Is the scroll event performant on lower-end devices? 低スペック端末でのスクロールパフォーマンスは問題ありませんか?
Yes, passive event listeners and requestAnimationFrame throttling ensure 60fps responsiveness without layout thrashing.
はい、passive: true イベントリスナーとシンプルなDOM幅更新により、レイアウト再計算を抑えて滑らかな60fpsを維持します。
AIへの指示テンプレート AI Prompt Template
以下をAIに貼り付けると実装を依頼できます。 Paste the following into your AI assistant to request implementation.