純CSS読書進捗バー(animation-timeline: scroll())

animation-timeline: scroll() で上部の進捗バーがスクロールに連動して伸びます。JavaScript無しで実装でき、長文記事の読了率表示に最適。

#css#scroll#progress

ライブデモ

使用例(お題: カフェ MOON BREW)

この技法を「カフェ MOON BREW」というテーマのダミーサイトで実際に使った例です。

HTML
<!-- MOON BREW ブランドストーリー。上部の読書進捗バーは純CSS(scroll())で伸びる -->
<div class="csp-scroller">
  <!-- 上部に固定された進捗バー。scroll()タイムラインで伸びる -->
  <div class="csp-track" aria-hidden="true">
    <div class="csp-bar"></div>
  </div>

  <article class="csp-article">
    <p class="csp-kicker">OUR STORY</p>
    <h1>満月の夜に、はじまった一杯</h1>
    <p class="csp-meta">MOON BREW について ・ 読了 約2分</p>

    <p>MOON BREW は、ある満月の夜に生まれた小さなコーヒースタンドです。仕事帰りに一息つける場所がほしい。そんな素朴な願いから、すべては始まりました。</p>
    <p>名前の由来は、夜遅くまで開けていた創業の日々。月明かりの下で淹れる一杯が、いつしか常連さんの合言葉になりました。</p>
    <p>豆は、信頼できる小規模農園から少量ずつ。焙煎はすべて店内で行い、香りがいちばん良い状態でお出しすることにこだわっています。</p>
    <p>定番の「ムーンドリップ」は、浅煎りの明るい酸味が持ち味。柑橘のような余韻が、ゆっくりと夜をほどいてくれます。</p>
    <p>季節ごとのメニューも大切にしています。春は桜、夏は柑橘、秋は栗、冬は焼き菓子と。めぐる季節を、一杯の中で楽しんでいただけたら。</p>
    <p>私たちが目指すのは、特別ではない、けれど確かにほっとできる時間。今日もどこかで、あなたの一杯をていねいに淹れています。</p>
    <p class="csp-end">— ようこそ、MOON BREW へ —</p>
  </article>
</div>
CSS
* { margin: 0; padding: 0; box-sizing: border-box; }

:root {
  --cream: #f5ede1;
  --brown: #2b1d12;
  --amber: #c98a3b;
}

body {
  font-family: "Hiragino Mincho ProN", "Yu Mincho", "Segoe UI", serif;
  background: var(--cream);
  color: var(--brown);
  -webkit-font-smoothing: antialiased;
}

/* 内部の縦スクロール領域。これが scroll() の基準になる */
.csp-scroller {
  position: relative;
  width: 100%;
  height: 100vh;
  max-height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: thin;
  scrollbar-color: var(--amber) transparent;
}

/* 上部に貼り付く進捗トラック */
.csp-track {
  position: sticky;
  top: 0;
  z-index: 5;
  height: 6px;
  background: rgba(43,29,18,.1);
}
.csp-bar {
  height: 100%;
  width: 100%;
  transform-origin: 0 50%;
  /* 非対応時は満タン表示。対応時はscroll()で0→1に伸びる */
  transform: scaleX(1);
  background: linear-gradient(90deg, #b9772b, var(--amber));
  box-shadow: 0 0 12px rgba(201,138,59,.55);
}

.csp-article {
  max-width: 560px;
  margin: 0 auto;
  padding: 40px 26px 120px;
}
.csp-kicker { font-family: "Segoe UI", sans-serif; letter-spacing: .3em; font-size: .64rem; color: var(--amber); }
.csp-article h1 { font-size: 1.8rem; font-weight: 700; line-height: 1.45; margin: 10px 0 6px; }
.csp-meta { font-family: "Segoe UI", sans-serif; font-size: .74rem; color: #9c8a72; margin-bottom: 24px; }
.csp-article p { font-size: .94rem; line-height: 1.95; color: #4d3a28; margin-bottom: 16px; }
.csp-article code {
  font-family: "Consolas", monospace;
  font-size: .82em;
  padding: 1px 6px;
  border-radius: 5px;
  background: rgba(201,138,59,.16);
  color: #8a5a1f;
}
.csp-end {
  text-align: center;
  letter-spacing: .16em;
  color: var(--amber);
  margin-top: 28px;
}

/* 対応ブラウザのみ:スクロール量に連動してバーを伸ばす */
@supports (animation-timeline: scroll()) {
  .csp-bar {
    transform: scaleX(0);
    animation: cspGrow linear both;
    /* 最寄りのスクロール祖先(.csp-scroller)を基準にする */
    animation-timeline: scroll(nearest);
  }
  @keyframes cspGrow {
    to { transform: scaleX(1); }
  }
}

/* prefers-reduced-motion でも進捗自体は表示(位置情報なので維持) */
JavaScript
// MOON BREW ストーリー:進捗バーは純CSS(animation-timeline: scroll())。
// JSは「操作前でも伸び方が見えるように」枠内を軽く自動スクロールするだけ。
(() => {
  const scroller = document.querySelector('.csp-scroller');
  if (!scroller) return; // null安全

  const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  if (reduce) return; // 動きを減らす設定では自動スクロールしない

  let auto = true;
  const stop = () => { auto = false; };
  ['wheel', 'touchstart', 'pointerdown'].forEach(ev =>
    scroller.addEventListener(ev, stop, { passive: true }));

  setTimeout(function step() {
    if (!auto) return;
    const max = scroller.scrollHeight - scroller.clientHeight;
    if (scroller.scrollTop >= max - 1) return; // 最下部で停止
    scroller.scrollTop += 1.4;
    requestAnimationFrame(step);
  }, 700);
})();

コード

HTML
<!-- 純CSS読書進捗バー。JS無し・内部に縦スクロール領域を作る -->
<div class="csp-scroller">
  <!-- 上部に固定された進捗バー。scroll()タイムラインで伸びる -->
  <div class="csp-track" aria-hidden="true">
    <div class="csp-bar"></div>
  </div>

  <article class="csp-article">
    <p class="csp-kicker">CSS SCROLL PROGRESS</p>
    <h1>純CSS 読書進捗バー</h1>
    <p class="csp-meta">animation-timeline: scroll() ・ JavaScript不要</p>

    <p>上部のバーは、この記事領域のスクロール量に完全連動して伸びます。JavaScriptのscrollイベントは一切使っていません。</p>
    <p>仕組みはシンプルで、進捗バーに <code>animation-timeline: scroll()</code> を割り当て、scaleXを0から1へ変化させるキーフレームを紐づけているだけです。</p>
    <p>スクロールの進捗そのものがアニメーションの再生位置になるため、巻き戻しても完全に同期します。慣性スクロールでも遅延なく追従します。</p>
    <p>コンポジタスレッドで処理されるので軽量で、長文記事やドキュメントの読了率を示すUXとして定番になりつつあります。</p>
    <p>非対応ブラウザでは <code>@supports</code> ガードによりバーが満タンのまま静止し、レイアウトは崩れません。</p>
    <p>さらに下までスクロールすると、進捗バーがちょうど右端まで到達します。読み終わりが視覚的に分かります。</p>
    <p>このように、これまでJSが必要だった演出も、スクロール駆動アニメーションで宣言的に書けるようになりました。</p>
    <p class="csp-end">— 読了 —</p>
  </article>
</div>
CSS
* { margin: 0; padding: 0; box-sizing: border-box; }

:root {
  --bg: #0b1020;
  --ink: #eef1ff;
  --accent: #f472b6;
  --accent2: #facc15;
}

body {
  font-family: "Segoe UI", system-ui, sans-serif;
  background: var(--bg);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
}

/* 内部の縦スクロール領域。これが scroll() の基準になる */
.csp-scroller {
  position: relative;
  width: 100%;
  height: 100vh;
  max-height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: thin;
}

/* 上部に貼り付く進捗トラック */
.csp-track {
  position: sticky;
  top: 0;
  z-index: 5;
  height: 6px;
  background: rgba(255,255,255,.08);
  backdrop-filter: blur(2px);
}
.csp-bar {
  height: 100%;
  width: 100%;
  transform-origin: 0 50%;
  /* 非対応時は満タン表示。対応時はscroll()で0→1に伸びる */
  transform: scaleX(1);
  background: linear-gradient(90deg, var(--accent), var(--accent2));
  box-shadow: 0 0 14px rgba(244,114,182,.6);
}

.csp-article {
  max-width: 560px;
  margin: 0 auto;
  padding: 40px 26px 120px;
}
.csp-kicker { letter-spacing: .32em; font-size: .66rem; color: var(--accent2); }
.csp-article h1 { font-size: 1.9rem; font-weight: 800; margin: 10px 0 6px; }
.csp-meta { font-size: .76rem; color: #9aa0c8; margin-bottom: 26px; }
.csp-article p { font-size: .94rem; line-height: 1.85; color: #cdd1ec; margin-bottom: 18px; }
.csp-article code {
  font-family: "Consolas", monospace;
  font-size: .82em;
  padding: 1px 6px;
  border-radius: 5px;
  background: rgba(244,114,182,.16);
  color: #ffd6ea;
}
.csp-end {
  text-align: center;
  letter-spacing: .3em;
  color: var(--accent);
  margin-top: 30px;
}

/* 対応ブラウザのみ:スクロール量に連動してバーを伸ばす */
@supports (animation-timeline: scroll()) {
  .csp-bar {
    transform: scaleX(0);
    animation: cspGrow linear both;
    /* 最寄りのスクロール祖先(.csp-scroller)を基準にする */
    animation-timeline: scroll(nearest);
  }
  @keyframes cspGrow {
    to { transform: scaleX(1); }
  }
}

/* prefers-reduced-motion でも進捗自体は表示(位置情報なので維持) */
JavaScript
// 進捗バーは純CSS(animation-timeline: scroll())。
// JSは「操作前でも伸び方が見えるように」枠内を軽く自動スクロールするだけ。
(() => {
  const scroller = document.querySelector('.csp-scroller');
  if (!scroller) return; // null安全

  const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  if (reduce) return; // 動きを減らす設定では自動スクロールしない

  let auto = true;
  const stop = () => { auto = false; };
  scroller.addEventListener('wheel', stop, { passive: true });
  scroller.addEventListener('touchstart', stop, { passive: true });
  scroller.addEventListener('pointerdown', stop);

  setTimeout(function step() {
    if (!auto) return;
    const max = scroller.scrollHeight - scroller.clientHeight;
    if (scroller.scrollTop >= max - 1) return; // 最下部で停止
    scroller.scrollTop += 1.4;
    requestAnimationFrame(step);
  }, 700);
})();

🤖 AIエージェント用プロンプト

このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「純CSS読書進捗バー(animation-timeline: scroll())」の効果を追加してください。

# 追加してほしい効果
純CSS読書進捗バー(animation-timeline: scroll())(スクロール演出)
animation-timeline: scroll() で上部の進捗バーがスクロールに連動して伸びます。JavaScript無しで実装でき、長文記事の読了率表示に最適。

# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- 純CSS読書進捗バー。JS無し・内部に縦スクロール領域を作る -->
<div class="csp-scroller">
  <!-- 上部に固定された進捗バー。scroll()タイムラインで伸びる -->
  <div class="csp-track" aria-hidden="true">
    <div class="csp-bar"></div>
  </div>

  <article class="csp-article">
    <p class="csp-kicker">CSS SCROLL PROGRESS</p>
    <h1>純CSS 読書進捗バー</h1>
    <p class="csp-meta">animation-timeline: scroll() ・ JavaScript不要</p>

    <p>上部のバーは、この記事領域のスクロール量に完全連動して伸びます。JavaScriptのscrollイベントは一切使っていません。</p>
    <p>仕組みはシンプルで、進捗バーに <code>animation-timeline: scroll()</code> を割り当て、scaleXを0から1へ変化させるキーフレームを紐づけているだけです。</p>
    <p>スクロールの進捗そのものがアニメーションの再生位置になるため、巻き戻しても完全に同期します。慣性スクロールでも遅延なく追従します。</p>
    <p>コンポジタスレッドで処理されるので軽量で、長文記事やドキュメントの読了率を示すUXとして定番になりつつあります。</p>
    <p>非対応ブラウザでは <code>@supports</code> ガードによりバーが満タンのまま静止し、レイアウトは崩れません。</p>
    <p>さらに下までスクロールすると、進捗バーがちょうど右端まで到達します。読み終わりが視覚的に分かります。</p>
    <p>このように、これまでJSが必要だった演出も、スクロール駆動アニメーションで宣言的に書けるようになりました。</p>
    <p class="csp-end">— 読了 —</p>
  </article>
</div>

【CSS】
* { margin: 0; padding: 0; box-sizing: border-box; }

:root {
  --bg: #0b1020;
  --ink: #eef1ff;
  --accent: #f472b6;
  --accent2: #facc15;
}

body {
  font-family: "Segoe UI", system-ui, sans-serif;
  background: var(--bg);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
}

/* 内部の縦スクロール領域。これが scroll() の基準になる */
.csp-scroller {
  position: relative;
  width: 100%;
  height: 100vh;
  max-height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: thin;
}

/* 上部に貼り付く進捗トラック */
.csp-track {
  position: sticky;
  top: 0;
  z-index: 5;
  height: 6px;
  background: rgba(255,255,255,.08);
  backdrop-filter: blur(2px);
}
.csp-bar {
  height: 100%;
  width: 100%;
  transform-origin: 0 50%;
  /* 非対応時は満タン表示。対応時はscroll()で0→1に伸びる */
  transform: scaleX(1);
  background: linear-gradient(90deg, var(--accent), var(--accent2));
  box-shadow: 0 0 14px rgba(244,114,182,.6);
}

.csp-article {
  max-width: 560px;
  margin: 0 auto;
  padding: 40px 26px 120px;
}
.csp-kicker { letter-spacing: .32em; font-size: .66rem; color: var(--accent2); }
.csp-article h1 { font-size: 1.9rem; font-weight: 800; margin: 10px 0 6px; }
.csp-meta { font-size: .76rem; color: #9aa0c8; margin-bottom: 26px; }
.csp-article p { font-size: .94rem; line-height: 1.85; color: #cdd1ec; margin-bottom: 18px; }
.csp-article code {
  font-family: "Consolas", monospace;
  font-size: .82em;
  padding: 1px 6px;
  border-radius: 5px;
  background: rgba(244,114,182,.16);
  color: #ffd6ea;
}
.csp-end {
  text-align: center;
  letter-spacing: .3em;
  color: var(--accent);
  margin-top: 30px;
}

/* 対応ブラウザのみ:スクロール量に連動してバーを伸ばす */
@supports (animation-timeline: scroll()) {
  .csp-bar {
    transform: scaleX(0);
    animation: cspGrow linear both;
    /* 最寄りのスクロール祖先(.csp-scroller)を基準にする */
    animation-timeline: scroll(nearest);
  }
  @keyframes cspGrow {
    to { transform: scaleX(1); }
  }
}

/* prefers-reduced-motion でも進捗自体は表示(位置情報なので維持) */

【JavaScript】
// 進捗バーは純CSS(animation-timeline: scroll())。
// JSは「操作前でも伸び方が見えるように」枠内を軽く自動スクロールするだけ。
(() => {
  const scroller = document.querySelector('.csp-scroller');
  if (!scroller) return; // null安全

  const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  if (reduce) return; // 動きを減らす設定では自動スクロールしない

  let auto = true;
  const stop = () => { auto = false; };
  scroller.addEventListener('wheel', stop, { passive: true });
  scroller.addEventListener('touchstart', stop, { passive: true });
  scroller.addEventListener('pointerdown', stop);

  setTimeout(function step() {
    if (!auto) return;
    const max = scroller.scrollHeight - scroller.clientHeight;
    if (scroller.scrollTop >= max - 1) return; // 最下部で停止
    scroller.scrollTop += 1.4;
    requestAnimationFrame(step);
  }, 700);
})();

# 外部ライブラリ
なし(追加ライブラリ不要)

# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。