CSSで進捗バーを作るには
外枠と中身の 2 要素だけで進捗バーを作り、進捗率をカスタムプロパティ 1 箇所で持たせる基本形を示す。
角丸を保ったまま中身をはみ出させない指定と、支援技術へ進捗を伝える属性も併せて扱う。
実行確認済みは、ブラウザで実際に描画して見た目を確認したものである。
公開:
<div
class="bar"
role="progressbar"
aria-label="ダウンロードの進捗"
aria-valuenow="64"
aria-valuemin="0"
aria-valuemax="100"
>
<div class="bar__fill"></div>
</div>
<p class="bar__label">64%</p>.bar {
/* 進捗率はここ 1 箇所。JS から変えるのもこの変数だけでよい */
--progress: 64%;
width: 100%;
height: 12px;
border-radius: 999px;
background: #e4e4e7;
/* 中身が角の外へ出ないように親で切り取る */
overflow: hidden;
}
.bar__fill {
width: var(--progress);
height: 100%;
/* 親と同じ角丸を受け継ぐ。数値を二度書かない */
border-radius: inherit;
background: #0f766e;
transition: width 0.3s ease;
}
.bar__label {
margin: 8px 0 0;
font-family: system-ui;
font-size: 13px;
color: #52525b;
}進捗バーは「溝になる外枠」と「伸びる中身」の 2 要素で作り、進捗率は外枠のカスタムプロパティ --progress にまとめて持たせる。
幅を中身の style 属性に直接書かないのは、進捗を更新する側が触る場所を 1 箇所に固定して、JS からも CSS からも同じ変数を書き換えれば済むようにするためである。
つまずき
中身が外枠の角からはみ出して見えるときは、外枠の overflow を確認する。
border-radius は要素の角を丸く描くだけで、子要素をその形に切り抜く働きはない。
子が親いっぱいに広がると、丸めた角の外側に子の直角の角がそのまま残る。
外枠に overflow: hidden を付けると、はみ出した部分が角の形で切り取られる。
子側にも border-radius: inherit を書いているのは、進捗が 100% 未満のときに右端だけ直角になるのを防ぐためである。
進捗率を更新する
進捗を動かすときに書き換えるのは --progress だけである。
JavaScript からは bar.style.setProperty('--progress', '80%') のように外枠へ指定すれば、中身の width が追従する。
中身の width を直接書き換える作りにすると、進捗を持つ場所が HTML 側と CSS 側に分かれてしまい、初期値をどちらに書いたか分からなくなる。
transition を中身に付けてあるので、値を変えた瞬間ではなく 0.3 秒かけて伸びる。
支援技術に進捗を伝える
div を 2 つ重ねただけでは、スクリーンリーダーには意味のない入れ物が 2 つあるとしか伝わらない。
外枠に role="progressbar" と aria-valuenow / aria-valuemin / aria-valuemax を付けると、現在値のある進捗として読み上げられる。
値を JavaScript で変えるときは aria-valuenow も同時に更新する必要がある。
--progress だけを変えて属性を放置すると、見た目と読み上げが食い違う。
なお HTML には progress 要素もあるが、ブラウザごとに既定の見た目が異なり細かく整えにくいため、ここでは div で組んでいる。