CSSで、ページを読んだ量を画面の上のバーで示す方法
ページをどこまで読んだかを画面の上のバーで示す表示は、CSS の animation-timeline: scroll() だけで作れます。バーを横に伸ばすアニメーションを用意し、その進み具合を時間ではなくページのスクロール量に結びつけます。JavaScript でスクロール量を計算する必要はありません。
検証環境は Ubuntu 24.04、Node.js 22.23.4、Playwright 1.63.0、Chromium 153 です。HTML と CSS のページを Playwright で開き、スクロールの位置ごとに、バーにかかっている transform の計算値を読み取りました。Firefox と Safari、スマートフォンの実機では試していません。

バーの CSS と HTML
HTML には、バーにする空の div を1つ置きます。中身のない飾りなので aria-hidden="true" を付けています。
<body>
<div class="reading-progress" aria-hidden="true"></div>
<header>サイトのヘッダー</header>
<main>
<!-- 本文 -->
</main>
<footer>サイトのフッター</footer>
</body>
CSS は次のとおりです。
/* ページを読んだ量を、画面の上のバーで示す */
@supports (animation-timeline: scroll()) {
.reading-progress {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 6px;
background: #00c8e8;
transform-origin: 0 50%;
/* スクロールできないページでは、タイムラインが働かず、この値のまま */
transform: scaleX(0);
z-index: 10;
animation: reading-progress 1ms linear both;
animation-timeline: scroll(root block);
}
}
@keyframes reading-progress {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
バーは position: fixed で画面の上端に固定し、幅は画面いっぱいにしておきます。長さは width ではなく transform: scaleX() で変えます。@keyframes で scaleX(0) から scaleX(1) へ伸ばすアニメーションを作り、animation-timeline でその進み具合をスクロールに結びつけます。
transform-origin: 0 50% は、左端を基準に伸ばすための指定です。transform-origin の初期値は 50% 50%(要素の真ん中)なので、書かないとバーは真ん中から左右へ広がります。
animation の一括指定にある 1ms は再生時間ではありません。MDN にある、スクロール駆動アニメーションのタイムラインの解説によると、スクロールに結びつけたアニメーションでは animation-duration は再生時間に影響しません。ただし、Firefox は 0 でない値がないとアニメーションを適用しないため、1ms を書くのが一般的とされています。
また、MDN の animation-timeline のページにあるとおり、animation の一括指定は animation-timeline を auto に戻します。animation-timeline は animation より後に書きます。
scroll(root block) で、ページ全体のスクロール量に合わせる
scroll() は、どの要素のスクロールを使うかと、縦と横のどちらのスクロールを使うかを選ぶ関数です。root は文書のルート要素、つまりページ全体のスクロールを選びます。block は、横書きのページでは縦方向のスクロールです。
幅400px、高さ800px の画面でスクロールの位置ごとに読み取ると、バーの長さはスクロールできる量に対する割合と同じになりました。このページでスクロールできる量は 3329px です。
scrollY=0 最大=3329 scaleX=0.000 幅=0.0px
scrollY=832 最大=3329 scaleX=0.250 幅=100.0px
scrollY=1665 最大=3329 scaleX=0.500 幅=200.1px
scrollY=2497 最大=3329 scaleX=0.750 幅=300.0px
scrollY=3329 最大=3329 scaleX=1.000 幅=400.0px
scroll() の引数を省くと、scroll(nearest block) と同じ意味になり、いちばん近い祖先のスクロールコンテナーを使います。このページでは、animation-timeline: scroll() にしても、5つの位置で値はまったく同じでした。それでも root と書いておくと、ページ全体のスクロールを使うことがコードから読み取れます。
ページ全体のスクロール量なので、本文の下にあるフッターの分も含まれます。高さ300px のフッターがあるこのページでは、本文の末尾が画面の下端に来たとき、バーは 91.0% でした。フッターまでスクロールしきったところで 100% になります。
バーが 100% になるのは、ページのいちばん下までスクロールしたときです。本文を読み終えた位置ではなく、ページ全体のどこまで進んだかを示します。
スクロールできないページでは、バーが端まで伸びる
ページが画面に収まり、スクロールできないときは、タイムラインが働きません。MDN の scroll() のページにも、指定した軸にスクロールバーがないと、アニメーションのタイムラインは無効になると書かれています。
検証環境で試したところ、タイムラインが無効なときは、アニメーションの値ではなく、要素にもともと指定した transform が使われました。transform を書いていないと計算値は none で、バーは画面の幅いっぱいに伸びたままになりました。上の CSS で transform: scaleX(0) を書いているのは、これを防ぐためです。書いておくと、スクロールできないページでは幅が 0 になり、バーは見えませんでした。
transform を書かない場合: 最大=0 scaleX=none(1) 幅=400.0px
transform: scaleX(0) を書いた場合: 最大=0 scaleX=0.000 幅=0.0px

対応していないブラウザでは、バーを出さない
上の CSS は、バーのスタイルをすべて @supports の中に書いています。animation-timeline: scroll() に対応していないブラウザでは、バーはスタイルのない空の div になり、見えません。
対応していないブラウザは検証環境に用意していないので、@supports の条件をわざと成り立たないもの(animation-timeline: no-such-value)に変えて確かめました。このとき、バーの div は position が static、高さが 0px、背景は透明で、スクロールしても変わりませんでした。
@supports で囲まずに書いた場合も確かめました。animation-timeline の値を無効なもの(no-such-value)にして、この宣言が使われない状態にすると、計算値は auto になりました。MDN の animation-timeline のページによると、auto は時間で進む文書タイムラインです。このアニメーションは 1ms で終わって scaleX(1) のまま止まるので、バーはスクロールの位置にかかわらず、画面の幅いっぱいに出たままでした。
スクロールする枠の中で読んだ量を示す
利用規約の枠のように、ページの中のスクロールする枠で読んだ量を示したいときは、scroll() を引数なしで使います。バーを枠の中に置くと、いちばん近い祖先のスクロールコンテナー、つまりその枠のスクロールにバーが合います。
<div class="terms" tabindex="0">
<div class="terms-progress" aria-hidden="true"></div>
<p>第1条 …</p>
</div>
/* スクロールする枠の中で、読んだ量を枠の上のバーで示す */
.terms {
height: 240px;
overflow-y: auto;
border: 1px solid #9aa5ab;
}
@supports (animation-timeline: scroll()) {
.terms-progress {
position: sticky;
top: 0;
height: 4px;
background: #00c8e8;
transform-origin: 0 50%;
transform: scaleX(0);
animation: reading-progress 1ms linear both;
/* いちばん近い祖先のスクロールコンテナー(.terms)を基準にする */
animation-timeline: scroll();
}
}
@keyframes はページ全体のバーと同じものです。枠の中では position: fixed ではなく position: sticky と top: 0 を使い、枠をスクロールしてもバーが枠の上端に残るようにします。

枠の中をスクロールすると、バーは枠のスクロールできる量に対する割合で伸びました。バーの位置は、どのスクロール位置でも枠の上端(枠線の1px 内側)のままでした。
枠のscrollTop=0 枠の最大=284 scaleX=0.000
枠のscrollTop=142 枠の最大=284 scaleX=0.500
枠のscrollTop=284 枠の最大=284 scaleX=1.000
ここで scroll(root) と書くと、枠のスクロールではなくページ全体のスクロールに合ってしまいます。試したところ、枠を最後までスクロールしてもバーは 0 のままで、ページのほうを 300px スクロールしたときに 39.3% まで伸びました。
対応しているブラウザ
MDN の互換性データ(@mdn/browser-compat-data 8.1.8)では、animation-timeline と scroll() は、どちらも次のバージョンから使えます。
| ブラウザ | バージョン |
|---|---|
| Chrome、Edge | 115 |
| Firefox | 159 |
| Safari(macOS、iOS) | 26 |
同じデータでは、Firefox 159 のリリース日は 2026年10月27日です。これより古いブラウザでは、@supports の中が使われないので、バーは表示されません。





