【コピペOK】プログレスバー css アニメーション 9選
プログレスバー css アニメーションは、スクロールした量だけを手がかりに「今どこまで読んだか」を見せるCSSの部品です。CSSだけ・JS不要で、上部バー・円形バッジ・点ナビ・縦バー・文字塗り・数値・章ステップ・目次・残り時間の9種類を集めました。
自動再生 · タイルを押すとその項目へ · すべて1つのzip
9つは人気順ではなく、読者が意識せずに視界へ入る場所から並べました。画面の端でいつも見えるもの(01〜04)の次に、本文の中で直接目に入るもの(05〜06)、最後に記事の構造そのものを説明するもの(07〜09)です。01・02・04・05・06・09の6つはanimation-timeline: scroll()ひとつだけでスクリプトなしに動き、03・07・08の3つはスクロール位置に応じてJSがクラスを切り替える方式です。scroll()をまだ使えないブラウザ向けの代替版は落とし穴の章にまとめてあります。
01上部の細いバー
画面最上部に張り付いた3pxのバーが、読んだ分だけ左から満ちていきます。実装はanimation-timeline: scroll(root)ひとつで、JavaScriptは使いません。長い記事のヘッダー直下や、チュートリアル文書の冒頭に向いています。
.bar {
width: 100%;
transform: scaleX(0);
transform-origin: left;
@supports (animation-timeline: scroll()) {
animation: fill linear both;
animation-timeline: scroll(root);
}
}
@keyframes fill { to { transform: scaleX(1); } }
02円形リングバッジ
画面右下に浮かぶ円形バッジが、conic-gradientのリングで読んだ分だけ満ちていきます。@property --pを数値として登録しているので、リングの角度がなめらかに補間されます。フローティングのトップへ戻るボタンと組み合わせると、ひとつのボタンで移動と進捗表示を兼ねられます。
@property --p { syntax: '<number>'; initial-value: 0; inherits: false; }
.badge {
--p: 0;
border-radius: 50%;
background: conic-gradient($color calc(var(--p) * 1%), $stage-paper 0);
@supports (animation-timeline: scroll()) {
animation: fill-ring linear both;
animation-timeline: scroll(root);
}
}
@keyframes fill-ring { from { --p: 0; } to { --p: 100; } }
03セクションドットナビ
縦に並んだ6つの点のうち、今読んでいるセクションの点だけが拡大して明るくなります。実装ではIntersectionObserverが画面に入ったセクションを判定し、対応する点に.is-activeを付けます。ワンページ構成のストーリーサイトや、ランディングページのセクションナビに向いています。
const io = new IntersectionObserver((entries) => {
const top = entries
.filter((e) => e.isIntersecting)
.sort((a, b) => b.intersectionRatio - a.intersectionRatio)[0];
if (top) setActive(sections.indexOf(top.target));
}, { threshold: [0.3, 0.6] });
sections.forEach((el) => io.observe(el));
04サイド縦バー
画面右端の細いトラックが、上から下へ向かって読んだ位置ぶんだけ満ちていきます。01の上部バーと同じscroll(root)の仕組みを、縦方向に使っただけです。ドキュメントサイトや、長い技術記事の右余白に向いています。
.rail__fill {
height: 100%;
transform: scaleY(0);
transform-origin: top;
@supports (animation-timeline: scroll()) {
animation: fill-y linear both;
animation-timeline: scroll(root);
}
}
@keyframes fill-y { to { transform: scaleY(1); } }
05文字塗りつぶし
見出しの文字色が、読んだ割合だけ左から塗り替わっていきます。background-clip: textで切り抜いたグラデーションを@property --pのパーセンテージで動かしているだけで、画像は一枚も使いません。ヒーローの見出しや、記事タイトルを強調したい場面に向いています。
@property --p { syntax: '<percentage>'; initial-value: 0%; inherits: false; }
.headline {
--p: 0%;
background-image: linear-gradient(90deg, $color var(--p), $dim var(--p));
background-clip: text;
-webkit-background-clip: text;
color: transparent;
}
06数値パーセント
右上の小さな数字が、スクロールした分だけ0から100まで整数で上がっていきます。@property --numを整数として登録しているので、小数点を挟まずに数字がひとつずつ切り替わります。長いアンケートやチュートリアルの進捗表示に向いています。
@property --num { syntax: '<integer>'; initial-value: 0; inherits: false; }
.counter__num {
--num: 0;
counter-reset: num var(--num);
&::before { content: counter(num) '%'; }
@supports (animation-timeline: scroll()) {
animation: count-up linear both;
animation-timeline: scroll(root);
}
}
@keyframes count-up { from { --num: 0; } to { --num: 100; } }
07章ステップ
上に並んだ4つの章番号のうち、今読んでいる章の丸だけが色で塗りつぶされます。色を瞬時に切り替えるだけでは変化する一瞬が短すぎて測定に引っかからなかったため、scale(1.25)のポップを挟んで変化区間を広げてあります。チュートリアルシリーズや、複数ステップのガイド記事に向いています。
@keyframes fill-2 {
0%, 25% { background: transparent; transform: scale(1); }
33% { background: $color; transform: scale(1.25); }
40%, 100% { background: $color; transform: scale(1); }
}
.step:nth-child(2) .step__circle { animation: fill-2 linear both; }
08目次ハイライト
サイドの目次で、今読んでいる見出しにだけ背景のピルがスライドして追従します。濃い単色のピルは2行にまたがる遷移の途中で文字とのコントラストが崩れたため、14%の薄いティントと左のボーダーに変えてあります。ドキュメントサイトの目次や、長いガイド記事のスクロールスパイに向いています。
.toc__pill {
position: absolute;
background: rgba(47, 109, 246, .14);
border-left: 3px solid $color;
animation: move $dur-loop ease-in-out infinite;
}
@keyframes move {
0%, 10% { transform: translateY(0); }
25%, 35% { transform: translateY($row); }
100% { transform: translateY(0); }
}
09残り読了時間
「残りN分」の数字が、スクロールするほど減っていきます。残り分数は全体の分数から読んだ割合を差し引いた値で、@property --minを整数として登録して滑らかに数えています。長い記事のヘッダーや、ニュースレターのWeb版に向いています。
@property --min { syntax: '<integer>'; initial-value: 12; inherits: false; }
.time__num {
--min: 12;
counter-reset: min var(--min);
&::before { content: counter(min); }
}
@keyframes count-down { from { --min: 12; } to { --min: 0; } }
どこで崩れるか — 落とし穴
01・02・04・05・06・09の6つはanimation-timeline: scroll()ひとつだけでJavaScriptなしに動きますが、この機能への対応はまだ足並みが揃っていません。実測(2026-09-10)は下の表の通りです。
| 機能 | Chrome・Edge | Safari | Firefox |
|---|---|---|---|
animation-timeline scroll() |
115以降 | 26以降(2025年9月) | 158以降、2026年9月時点の安定版はフラグの内側 |
@property |
85以降 | 16.4以降 | 128以降 |
対応バージョンの一覧はMDNのanimation-timeline対応表とMDNの@property対応表で確認できます。実際にコードを動かして計測しているときに踏んだ落とし穴もひとつあります。07の章ステップを色の瞬間切り替え(steps(1))だけで作ると、フレームごとに変化がある区間がわずか4枚しかなく、測定スクリプトに「ほとんど動いていない」と判定されました。切り替わる瞬間にscale(1.25)のポップを挟んで変化区間を広げたところ、16枚まで増えてようやく動きとして認識されました。scroll()にまだ対応していないブラウザでは、scroll()に頼る6つの進行表示が止まったままになるため、zipのreact/版は9つ全部についてwindowのscrollイベントやJSでの区間判定(03・08のようなIntersectionObserverなど)で同じ値を計算し直す代替コードを最初から積んであります。この値を反映済みの9ファイルを開く圧縮パスワードは3rmg234aで、下からzipひとつにまとめて受け取れます。
アクセシビリティ
9つともprefers-reduced-motion: reduceではアニメーションを止めます。01・02・04・05・06・07・09のように進捗を累積で満たしていくものは、進捗0の初期状態ではなく読み終えた最終状態で固定されます。一方、03の点ナビと08の目次のように今見ている区間だけを点灯させるものは、最初の項目が点灯した状態で固定されます。06の数値と09の残り時間のようにcounter()で描いた数字はスクリーンリーダーがそもそも読み上げられないため、数字自体はaria-hiddenで隠し、包む要素にrole="status"またはrole="progressbar"とaria-valuenowを別に付けています。08の目次で今読んでいる見出しを指す項目は実際の<a href="#見出しid">なので、キーボードだけでもそのままたどり着けます。03の点ナビの点は装飾のためaria-hiddenで隠されており、スクリーンリーダーやキーボード移動の対象にはなりません。
@media (prefers-reduced-motion: reduce) {
.bar, .rail__fill, .badge,
.headline, .counter__num, .time__num { animation: none !important; }
.bar, .rail__fill { transform: scaleX(1); }
}
prefers-reduced-motion自体の対応状況はMDNの該当ページで確認できます。ほかのCSSアニメーションを探すならカテゴリー一覧、このサイトが何を配っているかは紹介ページで説明しています。
FAQ
scroll()にまだ対応していないブラウザではどうなりますか?
animation-timeline: scroll()を使う6つ(01・02・04・05・06・09)は@supports (animation-timeline: scroll())で囲んであるため、対応がなければ中のアニメーションはただ適用されません。残り3つ(03・07・08)はもともとJSでスクロール位置を判定する方式です。zipのreact/版は9つ全部についてJSで同じ値を計算し直しているので、CSSの代わりにこちらのコンポーネントを使えばFirefoxを含むすべてのブラウザで進捗が動きます。
9つのうちいくつを同時に置いても大丈夫ですか?
01の上部バーと04の縦バーのように置き場所が重ならない組み合わせなら問題ありません。ただし06の数値と09の残り時間のように、同じ進捗を別の数字で見せるものは、どちらか一方だけにしてください。両方点けると、どちらの数字を見ればいいのか分かりにくくなります。
見出しが少ない記事でも08の目次ハイライトは使えますか?
H2が4つに満たないと、ピルの移動がほとんど起きず効果そのものが見えません。当サイトではH2が4つ以上ある記事にだけ08を付け、それより短い記事には01の上部バーだけを残す運用にしています。