<progress>
要素を使用して、カラー アダプティブでアクセス可能な読み込みバーを作成する方法について、基本的な概要を示します。
この投稿では、<progress>
要素を使用して、カラー アダプティブでアクセス可能な読み込みバーを作成する方法について検討します。デモをお試しのうえ、ソースを表示してください。
動画で視聴したい場合は、この投稿の YouTube バージョンをご利用ください。
概要
<progress>
要素は、完了を視覚的に確認および音声でユーザーに知らせます。この視覚的なフィードバックは、フォームの進捗状況を示す、情報のダウンロードまたはアップロードが表示される、さらには進行状況の量は不明だが作業がまだ進行中であることを示すなどのシナリオで役立ちます。
この GUI 課題では、既存の HTML <progress>
要素を使用して、ユーザー補助の労力を軽減しました。色とレイアウトを利用することで、組み込み要素のカスタマイズの限界を後押しし、コンポーネントをモダナイズして、デザイン システム内に収まるようにします。
![各ブラウザのライトタブとダークタブ。Safari、Firefox、Chrome の順でアダプティブ アイコンの概要を確認できます。](https://web.developers.google.cn/static/articles/building/a-loading-bar-component/image/light-dark-tabs-each-br-cd04389a437d1.png?authuser=6&hl=ja)
マークアップ
<progress>
要素を <label>
でラップすることにしたため、明示的な関係属性を省略して、暗黙的な関係を優先するようにしました。また、読み込み状態の影響を受ける親要素にもラベルを付け、スクリーン リーダー技術でその情報をユーザーに伝えられるようにしています。
<progress></progress>
value
が存在しない場合、要素の進行状況は不確定です。max
属性はデフォルトで 1 に設定されているため、進行状況は 0 ~ 1 の間になります。たとえば、max
を 100 に設定すると、範囲は 0 ~ 100 に設定されます。0 と 1 の制限内に収めることにし、進行状況の値を 0.5 か 50% に変換しました。
ラベルラップの進行状況
暗黙的な関係では、progress 要素は次のようなラベルでラップされます。
<label>Loading progress<progress></progress></label>
このデモでは、スクリーン リーダー専用のラベルを含めることにしました。
そのためには、ラベルのテキストを <span>
でラップし、スタイルを適用して画面外に配置するようにします。
<label>
<span class="sr-only">Loading progress</span>
<progress></progress>
</label>
WebAIM の次の関連 CSS がある場合:
.sr-only {
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
height: 1px;
width: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
}
読み込みの進行状況の影響を受ける領域
視力が健康な場合、進行状況インジケーターを関連要素やページ領域に関連付けるのは簡単ですが、視覚障がいのあるユーザーにとってはあまり明確ではありません。読み込み完了時に変更される最上位の要素に aria-busy
属性を割り当てることで、この問題を改善できます。さらに、aria-describedby
で進行状況と読み込みゾーンの関係を示します。
<main id="loading-zone" aria-busy="true">
…
<progress aria-describedby="loading-zone"></progress>
</main>
JavaScript から、タスクの開始時に aria-busy
を true
に切り替え、完了したら false
に切り替えます。
Aria 属性の追加
<progress>
要素の暗黙的なロールは progressbar
ですが、この暗黙的なロールがないブラウザに対しては、明示的に指定しました。また、indeterminate
属性を追加して、要素を明示的に不明な状態にしました。これは、要素に value
が設定されていないことを確認するよりも明確です。
<label>
Loading
<progress
indeterminate
role="progressbar"
aria-describedby="loading-zone"
tabindex="-1"
>unknown</progress>
</label>
JavaScript から進行状況要素をフォーカス可能にするには、tabindex="-1"
を使用します。これはスクリーン リーダー技術において重要です。進行状況の変化に応じて進行状況にフォーカスを合わせると、更新された進行状況がどこまで到達したのかがユーザーに通知されるためです。
スタイル
進行状況の要素は、スタイル設定において少し扱いにくい部分があります。組み込みの HTML 要素には、選択が難しい特別な非表示部分があり、多くの場合、限られたプロパティ セットしか提供されません。
Layout
レイアウト スタイルは、進行状況要素のサイズとラベルの位置をある程度柔軟に調整するためのものです。特別な完了状態が追加されます。これは、有用な視覚的な手がかりになりますが、必須ではありません。
<progress>
レイアウト
進行状況要素の幅はそのままにしておいて、デザインに必要なスペースに合わせて縮小および拡張できるようにします。組み込みのスタイルは、appearance
と border
を none
に設定することで削除されます。このようにすることで、ブラウザ間で要素を正規化できるようになります。これは、ブラウザごとに要素に独自のスタイルがあるためです。
progress {
--_track-size: min(10px, 1ex);
--_radius: 1e3px;
/* reset */
appearance: none;
border: none;
position: relative;
height: var(--_track-size);
border-radius: var(--_radius);
overflow: hidden;
}
_radius
の 1e3px
の値は、科学的数値表記を使用して大きな数値を表し、border-radius
は常に丸められます。1000px
と同じです。ここで使用するのは、設定して忘れるのに十分な大きさの値を使用することです(1000px
よりも書き込むのが短くなります)。また、必要に応じてさらに大きくすることもできます。3 を 4 に変更すると、1e4px
は 10000px
と同等になります。
overflow: hidden
が使用されており、そのスタイルは議論の余地があるものです。これにより、border-radius
値をトラックに渡す必要がなくなったり、塗りつぶし要素をトラッキングする必要がなくなったりするなど、いくつかの処理が簡単になりました。また、進行状況の子を要素の外に置くことはできません。このカスタムの進行状況要素の別の反復処理は、overflow: hidden
なしで実行できます。これにより、アニメーションや完了状態の改善が行われる可能性があります。
処理が完了しました
ここでは CSS セレクタが、最大値と値を比較して手間のかかる作業を行います。これらのセレクタが一致すると、処理は完了です。完了すると疑似要素が生成され、progress 要素の最後に追加されます。これにより、完了を視覚的にわかりやすく示すことができます。
progress:not([max])[value="1"]::before,
progress[max="100"][value="100"]::before {
content: "✓";
position: absolute;
inset-block: 0;
inset-inline: auto 0;
display: flex;
align-items: center;
padding-inline-end: max(calc(var(--_track-size) / 4), 3px);
color: white;
font-size: calc(var(--_track-size) / 1.25);
}
色
ブラウザでは進行状況要素に独自の色が適用され、1 つの CSS プロパティのみで明暗に適応します。これは、特別なブラウザ固有のセレクタを使用して構築できます。
ライトとダークのブラウザ スタイル
サイトでダーク / ライト アダプティブ <progress>
要素にオプトインするには、color-scheme
が必要です。
progress {
color-scheme: light dark;
}
単一プロパティの進捗の塗りつぶしの色
<progress>
要素の色合いを調整するには、accent-color
を使用します。
progress {
accent-color: rebeccapurple;
}
トラックの背景色が accent-color
に応じて明るいから暗に変わることに注意してください。ブラウザでは適切なコントラストが確保されており、非常にきれいです。
ライトカラーとダークカラーをフルにカスタマイズ
<progress>
要素に、トラックの色とトラックの進行状況の色の 2 つのカスタム プロパティを設定します。prefers-color-scheme
メディアクエリ内で、トラックとトラックの進行状況の新しい色の値を指定します。
progress {
--_track: hsl(228 100% 90%);
--_progress: hsl(228 100% 50%);
}
@media (prefers-color-scheme: dark) {
progress {
--_track: hsl(228 20% 30%);
--_progress: hsl(228 100% 75%);
}
}
フォーカス スタイル
先ほど、要素に負のタブインデックスを割り当てて、プログラムでフォーカスできるようにしました。:focus-visible
を使用してフォーカスをカスタマイズし、スマートなフォーカス リング スタイルにオプトインします。これにより、マウスのクリックやフォーカスではフォーカス リングは表示されませんが、キーボードのクリックではフォーカス リングが表示されます。これについては YouTube 動画で詳しく説明しているため、確認することをおすすめします。
progress:focus-visible {
outline-color: var(--_progress);
outline-offset: 5px;
}
ブラウザをまたいだカスタム スタイル
スタイルをカスタマイズするには、各ブラウザが公開する <progress>
要素の要素を選択します。progress 要素の使用は単一のタグですが、CSS 疑似セレクタを介して公開されるいくつかの子要素で構成されています。設定を有効にすると、Chrome DevTools に次の要素が表示されます。
- ページを右クリックして [要素を検証] を選択すると、DevTools が表示されます。
- DevTools ウィンドウの右上にある設定の歯車アイコンをクリックします。
- [要素] の見出しで、[ユーザー エージェントの Shadow DOM を表示する] チェックボックスを見つけてオンにします。
Safari と Chromium のスタイル
Safari や Chromium などの WebKit ベースのブラウザでは、::-webkit-progress-bar
と ::-webkit-progress-value
が公開されているため、CSS のサブセットを使用できるようになります。ここでは、さきほど作成したカスタム プロパティを使用して background-color
を設定します。このプロパティはライトとダークに適応します。
/* Safari/Chromium */
progress[value]::-webkit-progress-bar {
background-color: var(--_track);
}
progress[value]::-webkit-progress-value {
background-color: var(--_progress);
}
Firefox のスタイル
Firefox では、<progress>
要素の ::-moz-progress-bar
疑似セレクタのみが公開されます。また、トラックの色合いを直接調整することもできません。
/* Firefox */
progress[value]::-moz-progress-bar {
background-color: var(--_progress);
}
Firefox では accent-color
からトラック色が設定されていますが、iOS Safari では薄い青色のトラックになっています。ダークモードでも同じです。Firefox にはダークトラックがありますが、設定したカスタム色はありません。また、Webkit ベースのブラウザで動作します。
アニメーション
ブラウザの組み込み疑似セレクタを使用する場合、多くの場合、許可される CSS プロパティが制限されます。
トラックをいっぱいにまでアニメーション化する
進行状況要素の inline-size
への遷移の追加は、Chromium では機能しますが、Safari では機能しません。また、Firefox では ::-moz-progress-bar
の transition プロパティを使用しません。
/* Chromium Only 😢 */
progress[value]::-webkit-progress-value {
background-color: var(--_progress);
transition: inline-size .25s ease-out;
}
:indeterminate
状態のアニメーション化
アニメーションを追加できるように、もう少しクリエイティブな表現にします。Chromium の疑似要素が作成され、3 つのブラウザすべてで前後にアニメーション表示されるグラデーションが適用されます。
カスタム プロパティ
カスタム プロパティはさまざまな用途に利用できますが、私のお気に入りの 1 つは、魔法のように見える CSS 値に名前を付けることです。以下はかなり複雑な linear-gradient
ですが、適切な名前が付いています。その目的とユースケースを明確に理解できる。
progress {
--_indeterminate-track: linear-gradient(to right,
var(--_track) 45%,
var(--_progress) 0%,
var(--_progress) 55%,
var(--_track) 0%
);
--_indeterminate-track-size: 225% 100%;
--_indeterminate-track-animation: progress-loading 2s infinite ease;
}
カスタム プロパティを使用すると、コードを DRY にすることができます。繰り返しになりますが、これらのブラウザ固有のセレクタをグループ化することはできないためです。
キーフレーム
前後に繰り返す無限のアニメーションが目標です。開始キーフレームと終了キーフレームは
CSS で設定しますキーフレームは 1 つ(50%
の中央のキーフレーム)だけで済み、開始位置に戻るアニメーションを何度か繰り返すことができます。
@keyframes progress-loading {
50% {
background-position: left;
}
}
各ブラウザをターゲットに設定
一部のブラウザでは、<progress>
要素自体で疑似要素を作成したり、進行状況バーをアニメーション化したりできます。トラックのアニメーション化に対応しているブラウザは擬似要素よりも多くなるため、ベースとして擬似要素からアニメーション バーにアップグレードします。
Chromium 疑似要素
Chromium では、要素を覆う位置に疑似要素 ::after
を使用できます。不確定のカスタム プロパティが使用され、前後アニメーションがうまく機能します。
progress:indeterminate::after {
content: "";
inset: 0;
position: absolute;
background: var(--_indeterminate-track);
background-size: var(--_indeterminate-track-size);
background-position: right;
animation: var(--_indeterminate-track-animation);
}
Safari の進行状況バー
Safari の場合、カスタム プロパティとアニメーションは擬似要素の進行状況バーに適用されます。
progress:indeterminate::-webkit-progress-bar {
background: var(--_indeterminate-track);
background-size: var(--_indeterminate-track-size);
background-position: right;
animation: var(--_indeterminate-track-animation);
}
Firefox の進行状況バー
Firefox では、カスタム プロパティとアニメーションが疑似要素の進行状況バーにも適用されます。
progress:indeterminate::-moz-progress-bar {
background: var(--_indeterminate-track);
background-size: var(--_indeterminate-track-size);
background-position: right;
animation: var(--_indeterminate-track-animation);
}
JavaScript
JavaScript は、<progress>
要素において重要な役割を果たします。要素に送信される値を制御し、スクリーン リーダー向けのドキュメントに十分な情報が存在するようにします。
const state = {
val: null
}
このデモでは、進行状況を制御するボタンを用意しています。ボタンで state.val
を更新してから、DOM を更新する関数を呼び出します。
document.querySelector('#complete').addEventListener('click', e => {
state.val = 1
setProgress()
})
setProgress()
この関数で UI/UX オーケストレーションが行われます。まず、setProgress()
関数を作成します。このオブジェクトは state
オブジェクト、進行状況要素、<main>
ゾーンにアクセスできるため、パラメータは必要ありません。
const setProgress = () => {
}
<main>
ゾーンの読み込みステータスの設定
進行状況が完了したかどうかに応じて、関連する <main>
要素で aria-busy
属性を更新する必要があります。
const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)
}
読み込み量が不明な場合は属性をクリアしてください
値が不明であるか、設定されていない場合、この使用方法の null
では、value
属性と aria-valuenow
属性を削除します。これにより、<progress>
が不確定になります。
const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)
if (state.val === null) {
progress.removeAttribute('aria-valuenow')
progress.removeAttribute('value')
progress.focus()
return
}
}
JavaScript の小数数学の問題を解決する
進行状況のデフォルトの最大値は 1 のままにしたため、デモのインクリメント関数とデクリメント関数では小数の計算を使用します。JavaScript などの言語は、こうした点で優れているとは限りません。次の roundDecimals()
関数は、計算結果の余分な部分をカットします。
const roundDecimals = (val, places) =>
+(Math.round(val + "e+" + places) + "e-" + places)
読みやすく、表示できるように値を丸めます。
const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)
if (state.val === null) {
progress.removeAttribute('aria-valuenow')
progress.removeAttribute('value')
progress.focus()
return
}
const val = roundDecimals(state.val, 2)
const valPercent = val * 100 + "%"
}
スクリーン リーダーとブラウザの状態の値を設定する
この値は DOM の 3 つの場所で使用されます。
<progress>
要素のvalue
属性。aria-valuenow
属性。<progress>
内部テキスト コンテンツ。
const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)
if (state.val === null) {
progress.removeAttribute('aria-valuenow')
progress.removeAttribute('value')
progress.focus()
return
}
const val = roundDecimals(state.val, 2)
const valPercent = val * 100 + "%"
progress.value = val
progress.setAttribute('aria-valuenow', valPercent)
progress.innerText = valPercent
}
進捗に焦点を当てる
値が更新されると、視力のあるユーザーには進行状況の変化が表示されますが、スクリーン リーダーのユーザーには変更の通知がまだ通知されません。<progress>
要素にフォーカスすると、ブラウザに更新が通知されます。
const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)
if (state.val === null) {
progress.removeAttribute('aria-valuenow')
progress.removeAttribute('value')
progress.focus()
return
}
const val = roundDecimals(state.val, 2)
const valPercent = val * 100 + "%"
progress.value = val
progress.setAttribute('aria-valuenow', valPercent)
progress.innerText = valPercent
progress.focus()
}
おわりに
どのようにやり方をしたかわかったので、どのように感じますか? ‽ 🙂?
機会があれば少しだけ変更したい点がございます。現在のコンポーネントをクリーンアップする余地と、<progress>
要素の疑似クラスのスタイル制限なしで構築を試みる余地があると思います。ぜひお試しください。
アプローチを多様化して、ウェブで構築するすべての方法を学びましょう。
デモを作成し、ツイートしてリンクをツイートしてください。以下のコミュニティ リミックスのセクションに追加いたします。