GODRICH

CSS プログレスバー 円形ゲージ9選 — 数値を変えるだけ

css プログレスバー 円で探している人が見たいのは、直線の帯ではなく丸いリングやゲージで割合を示すCSS部品です。今回は実際に動く9種類を用意し、どれもwidthではなくtransformとconic-gradientだけで値を表現しています。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

9つは人気順ではなく、値の扱い方が難しくなる順に並べました。帯が1本だけ伸び縮みする2つ(01・02)から始まり、形をリングと半円に変えた2つ(03・04)、値が段階しかない・そもそも分からないという2つ(05・06)を経て、数字やラベル、複数ステップと組み合わせた3つ(07〜09)で終わります。コードは要点だけを載せ、完全なファイル一式はzipにあります。

01基本の線形の塗り

塗りつぶしバーが --percent カスタムプロパティの値だけ scaleX で伸び、スクロールではなくアップロードなど実際の進捗数値をそのまま反映します。ファイルアップロードの進捗表示やフォーム送信の進行状況に向いています。

scaleX@property2초 루프
@property --percent {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}
.track { --percent: 0; position: relative; overflow: hidden; }
.bar {
  position: absolute; inset: 0;
  transform: scaleX(calc(var(--percent) / 100));
  transform-origin: left center;
}
.track.is-demo { animation: fill $duration $easing infinite; }
@keyframes fill {
  0%   { --percent: 0; }
  60%  { --percent: 76; }
  100% { --percent: 76; }
}

02ストライプアニメーション

斜めの縞模様の背景が background-position で絶えず流れ、値が変わらない区間でも何かが進行中だという印象を与えます。大量データ処理の表示やバックグラウンド同期のバーに向いています。

background-positionrepeating-linear-gradient1초 루프
.bar {
  position: absolute; inset: 0;
  background-image: repeating-linear-gradient(45deg, $color 0 10px, rgba($color, .55) 10px 20px);
  background-size: 28px 28px;
}
.bar.is-demo { animation: stripe-move $duration $easing infinite; }
@keyframes stripe-move {
  from { background-position: 0 0; }
  to   { background-position: 28px 0; }
}

03conic-gradientリング

ドーナツ型のリングが --percent の値まで満たされますが、数字は一切表示しません。値が低・中・高の3ゾーンを通過するたびに conic-gradient の色そのものが変わり、青(低)から黄色(中)を経て、最も高い区間では赤ではなく濃いインク色になります。サーバーのリソース使用率やバッテリー・ストレージの残量表示に向いています。

conic-gradient색 구간 전환@property
@property --percent {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}
.ring {
  --percent: 0; --zone: #{$color};
  background: conic-gradient(var(--zone) calc(var(--percent) * 1%), rgba(255, 255, 255, .35) 0);
}
.ring.is-demo { animation: sweep $duration $easing infinite, zone $duration steps(1) infinite; }
@keyframes zone {
  40%  { --zone: #{$color}; }
  41%  { --zone: #{$stage-yellow}; }
  75%  { --zone: #{$subject-ink}; }
}

04半円ゲージ

半円の目盛り盤の上で、針が --percent の値に合わせて rotate で回ります。スピードメーターのように片方の端からもう片方の端へだけ動き、一周はしません。速度やスコアのゲージ、目標達成度のダッシュボードに向いています。

transform: rotate()반원 clip바늘
@property --percent {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}
.gauge { --percent: 0; overflow: hidden; }
.gauge::before {
  width: 100%; height: 200%; border-radius: 50%;
  background: conic-gradient(from -90deg, $color calc(var(--percent) * 1.8deg), rgba($subject-ink, .12) 0);
}
.gauge__needle {
  transform-origin: bottom center;
  transform: translateX(-50%) rotate(calc(var(--percent) * 1.8deg - 90deg));
}

05セグメントステップ

バーは1本の帯ではなく5つの区切りに分かれ、値が上がるたびに区切りが順番にひとつずつ点灯します。電波強度の表示や、体力ゲージのようなゲームUIに向いています。

opacity 단계 전환animation-delay조각 5개
.seg {
  width: 16px; height: 46px; border-radius: 4px;
  opacity: .35; transform: scaleY(.7); transform-origin: bottom;
}
.segs.is-demo .seg { animation: light 2s $easing infinite; }
.segs.is-demo .seg:nth-child(2) { animation-delay: 160ms; }
@keyframes light {
  0%, 60%, 100% { opacity: .35; transform: scaleY(.7); }
  20%, 45%      { opacity: 1;   transform: scaleY(1); background: $color; }
}

06不定バー

1本の短いバーが左右に往復する代わりに、長さの異なる2本の帯が時間差を付けて重なりながら通過します。マテリアルデザインのローディングバーに近い転がる印象を作り、所要時間が分からない初期読み込みや検索結果の待機に向いています。

두 세그먼트animation-delay 차등cubic-bezier
.seg {
  position: absolute; inset: 0;
  background: $color; transform-origin: left center;
}
.seg--a.is-demo { animation: seg-a $duration $easing infinite; }
.seg--b.is-demo { animation: seg-b $duration $easing infinite; animation-delay: $duration * .35; }
@keyframes seg-a {
  0%   { transform: translateX(-100%) scaleX(.15); }
  55%  { transform: translateX(30%)   scaleX(.7); }
  100% { transform: translateX(120%)  scaleX(.15); }
}

07パーセントラベル連動

リングではなく横バーの上で、塗りの先端を追いかけるように吹き出しラベルが translateX で一緒に動き、今の値を数字で示します。アンケートやオンボーディングの進行表示、スライダー型入力のプレビューに向いています。

translateX 동기말풍선CSS 변수
@property --percent {
  syntax: "<integer>";
  inherits: true;
  initial-value: 0;
}
.track { --percent: 0; container-type: inline-size; }
.bar { transform: scaleX(calc(var(--percent) / 100)); transform-origin: left center; }
.bubble {
  position: absolute; top: -30px; left: 0;
  transform: translateX(calc(var(--percent) * 1cqw - 50%));
}
.bubble__num { counter-reset: p var(--percent); &::before { content: counter(p) "%"; } }

08ステッパー進捗

番号付きの丸3つが線でつながり、完了した段階ごとに丸が塗りつぶされて、その間の連結線が scaleX で伸びます。前の段階が終わるまで次の丸は動き始めないため、会員登録の複数ステップフォームやチェックアウトの段階表示に向いています。

scaleX 연결선원 채움단계 3개
.stepper.is-demo .step__dot { animation: dot-fill $duration $easing both; }
.stepper.is-demo .line { animation: line-grow $duration linear both; }
.stepper.is-demo > :nth-child(2) { animation-delay: 400ms; }
.stepper.is-demo > :nth-child(3) .step__dot { animation-delay: 800ms; }
@keyframes dot-fill {
  from { background: rgba(255, 255, 255, .14); transform: scale(.85); }
  to   { background: $color; transform: scale(1); }
}
@keyframes line-grow { from { transform: scaleX(0); } to { transform: scaleX(1); } }

09バッテリー型ゲージ

バッテリー型の枠の中の塗りが scaleX で満ち、残量が少なくなるほど色がクリーム色から黄色、警告用の赤茶色へと変わります。残量が少ないときだけ枠がわずかに点滅し、機器のバッテリー表示やリソース枯渇の警告ウィジェットに向いています。

scaleX색 임계값 전환테두리 펄스
@property --percent {
  syntax: "<number>";
  inherits: false;
  initial-value: 100;
}
.battery__fill {
  --percent: 100;
  transform: scaleX(calc(var(--percent) / 100));
  animation: drain $duration $easing infinite;
}
@keyframes drain {
  0%   { --percent: 100; background: #{$color}; }
  55%  { --percent: 35;  background: #{$stage-yellow}; }
  100% { --percent: 8;   background: #{$error}; }
}

どこで崩れるか — 落とし穴

9つを組んでいて実際に踏んだ落とし穴は2種類です。1つ目は06の不定バーです。重なって流れる2本の帯をposition: absoluteで浮かせただけでは、leftrightを指定しない絶対配置要素は中身の大きさのぶんしか幅を持たず、空の<span>は幅0のままscaleXをいくら掛けても画面に何も現れません。inset: 0で先に幅を親いっぱいに広げ、そこからtransformだけで押し出して縮めるようにしてから帯が見え始めました。2つ目は@propertyinheritsです。03と09は--percentを持つ要素自身が背景やリングを描くだけなのでinherits: falseで構いませんが、04の針や07の吹き出し・counter()は親の.gauge.trackが動かす値を子要素が読み取る構造のため、inherits: trueにしないとMDNの@property文書が示すとおり子は初期値のまま止まります。9ファイルとも構造に合わせてinheritsを使い分けてあり、開くための圧縮パスワードはbetrch52です。

アクセシビリティ

01〜05・07・09の7つはrole="progressbar"aria-valuenowを添えて今の値を伝えますが、値が定まらない06の不定バーだけはaria-valuenowaria-valueminaria-valuemaxも持ちません。08のステッパーだけは進捗バーではなくrole="list"role="listitem"の並びで、番号付きの丸とラベルで段階そのものを伝えます。

<div class="track is-demo" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="76" aria-label="アップロード中">
  <div class="bar"></div>
</div>

prefers-reduced-motion: reduceでは、03の色分けリングと09のバッテリーはそのとき止まる値に応じた色のまま固定されます。05のセグメントは動きを止めると逆にすべての区切りが点灯した状態になり、08のステッパーも全ての丸と線が完了済みの見た目に切り替わります。06の不定バーだけは値を持たないため、幅40%ほどの帯が半透明のまま静止するだけです。

CSSアニメーションの他の記事はカテゴリー一覧に、このサイトの成り立ちは紹介ページにまとめています。

FAQ

@propertyを使わずにこのゲージを作れますか?

はい、可能ですが値が切り替わる瞬間に段階を飛ばして一気に変わります。@propertyで数値として登録すると、ブラウザが毎フレームの中間値を自動で計算し、滑らかに塗りが伸びます。登録なしで同じ滑らかさを出すにはrequestAnimationFrameで自前に値を書き換えるJavaScriptが必要です。MDNの@property対応表によるとChrome・Edgeは85以降、Safariは16.4以降、Firefoxは128以降で使え、caniuseの対応表でも同じ数値を確認できます。

半円ゲージと円形ゲージ、どちらを選べばいいですか?

値の範囲がひとつの指標を示すだけなら半円のほうが読み取りやすく、使用率やバッテリーのように低い・警告といった状態を色でも区別したい場合は円形のほうが省スペースです。同じ画面に両方の形を混ぜると、どちらが何を示しているのか伝わりにくくなるので避けたほうが無難です。

値をサーバーから受け取ってリアルタイムに変えるには?

React版のようにpercentをpropsや状態として受け取り、インラインスタイルの--percentの値だけを書き換えれば十分です。@propertyで登録済みのカスタムプロパティは値が変わるたびにtransitionを掛けておくだけで新しい値まで自動的に補間されるので、独自のアニメーションループを組む必要はありません。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。