読み込みバー コンポーネントの作成

<progress> 要素を使用して、アダプティブでアクセス可能な読み込みバーを作成する方法の基本的な概要です。

この投稿では、<progress> 要素を使用して、カラー アダプティブでアクセス可能な読み込みバーを作成する方法について考えたいと思います。デモをお試しください。また、ソースを確認してください。

Chrome でライト / ダーク、不確定、増加、完了のデモを行いました。

動画をご覧になる場合は、この投稿の YouTube バージョンをご覧ください。

概要

<progress> 要素は、完了についてユーザーに視覚的と音声によるフィードバックを提供します。この視覚的なフィードバックは、フォームでの進行状況、ダウンロードやアップロードの情報の表示、進行状況が不明だが作業はアクティブであることを示すなどのシナリオで役立ちます。

この GUI Challenge では、既存の HTML <progress> 要素を使用して、ユーザー補助機能の労力を軽減しました。色とレイアウトにより、組み込み要素のカスタマイズの限界が押し広げられ、コンポーネントがモダナイズされ、デザイン システム内に収まりやすくなります。

各ブラウザのライトタブとダークタブ。アダプティブ アイコンの概要を上から下へ表示する(Safari、Firefox、Chrome)。
デモは、ライトモードとダークモードの Firefox、Safari、iOS Safari、Chrome、Android Chrome で表示されています。

マークアップ

<progress> 要素を <label> にラップして、暗黙的な関係を優先して明示的な関係属性をスキップできるようにしました。また、読み込み状態の影響を受ける親要素にラベルを付け、スクリーン リーダー技術がその情報をユーザーに中継できるようにしています。

<progress></progress>

value が存在しない場合、要素の進行状況は不確定です。max 属性はデフォルトで 1 に設定されるため、進行状況は 0 ~ 1 になります。たとえば、max を 100 に設定すると、範囲は 0 ~ 100 に設定されます。私は 0 と 1 の制限内に留まり、進行状況の値を 0.5 または 50% に変換することにしました。

ラベルラップの進行状況

暗黙的な関係では、進行状況の要素が次のようなラベルでラップされます。

<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;
}

「画面の準備完了のみ」の要素が表示されている DevTools のスクリーンショット。

読み込みの進行状況の影響を受ける領域

視力が健全であれば、進行状況インジケーターを関連する要素やページ領域に関連付けるのは簡単ですが、視覚障がい者にとってはそれほど明確ではありません。読み込み完了時に変更される最上位の要素に aria-busy 属性を割り当てることで、この問題を改善できます。さらに、aria-describedby で進行状況と読み込みゾーンの関係を示します。

<main id="loading-zone" aria-busy="true">
  …
  <progress aria-describedby="loading-zone"></progress>
</main>

JavaScript から、タスクの開始時に aria-busytrue に切り替え、終了したら false に切り替えます。

Aria 属性の追加

<progress> 要素の暗黙的なロールは progressbar ですが、暗黙的なロールを持たないブラウザ向けに明示的に指定しました。また、属性 indeterminate を追加して、要素を明示的に「不明」の状態にしました。これは、value が設定されていないことを観察する場合よりも明確になります。

<label>
  Loading 
  <progress 
    indeterminate 
    role="progressbar" 
    aria-describedby="loading-zone"
    tabindex="-1"
  >unknown</progress>
</label>

tabindex="-1" を使用して、JavaScript から進行状況要素をフォーカスできるようにします。これは、スクリーン リーダーの技術では重要です。進行状況が変化したときに進行状況をフォーカスすると、進行状況がどこまで到達したかがユーザーに通知されるためです。

スタイル

進行状況の要素は、スタイルに関してはやや複雑です。組み込みの HTML 要素には特別な隠し部分があり、選択が難しいことがあり、多くの場合、設定できるプロパティは限られています。

レイアウト

レイアウト スタイルは、進行状況要素のサイズとラベルの位置にある程度の柔軟性を持たせるためのものです。特別な完了状態が追加されます。これは、必須ではありませんが、視覚的な手がかりになります。

<progress> レイアウト

進行状況要素の幅は何も変更しないので、デザインに必要なスペースに合わせて縮小 / 拡大できます。組み込みのスタイルは、appearancebordernone に設定することで取り除かれています。各ブラウザには独自のスタイルがあるため、これはブラウザ間で要素を正規化できるようにするためです。

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;
}

_radius1e3px の値は、科学的数表記を使用して大きな数値を表すため、border-radius は常に丸められます。これは 1000px と同じです。目的が、一度設定して消去できるほど大きな値を使用する(1000px よりも短く記述するため)ので、これを使用するのがいいです。また、必要に応じてさらに大きくすることも簡単です。3 を 4 に変更するだけで、1e4px10000px に相当します。

overflow: hidden は使用されており、これまで議論を重ねたスタイルでした。これにより、border-radius 値をトラックに送る必要がなくなり、フィル要素を追跡する必要がなくなりましたが、進行状況の子が要素の外に存在することがないようにしました。このカスタムの進行状況要素の別のイテレーションは、overflow: hidden なしで行うことができます。これにより、アニメーションを実現したり、完了状態を改善したりできます。

処理が完了しました

ここでは CSS セレクタが、最大値と値を比較することで難しい処理を行い、一致した場合は処理を完了します。完了すると擬似要素が生成され、進行状況要素の最後に追加されます。これにより、完了を視覚的に促すことができます。

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);
}

読み込みバーが 100% になり、最後にチェックマークが表示されているスクリーンショット。

ブラウザでは進行状況の要素に独自の色が使用され、1 つの CSS プロパティだけで明るい色と暗い色に適応します。これは、ブラウザ固有の特別なセレクタを使用して構築できます。

ライトモードとダークモードのブラウザのスタイル

サイトにダークとライトのアダプティブな <progress> 要素をオプトインするために必要なのは、color-scheme のみです。

progress {
  color-scheme: light dark;
}

1 つのプロパティの進行状況を示す塗りつぶしの色

<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> 要素の要素を選択します。進行状況要素は単一のタグですが、CSS 疑似セレクタを介して公開されるいくつかの子要素で構成されています。この設定を有効にすると、Chrome DevTools に次の要素が表示されます。

  1. ページを右クリックして [要素を検証] を選択し、DevTools を起動します。
  2. DevTools ウィンドウの右上にある設定の歯車アイコンをクリックします。
  3. [要素] で、[ユーザー エージェント Shadow DOM を表示する] チェックボックスを探してオンにします。

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 のスクリーンショットと進行状況要素のパーツの場所。

Android 版 Safari、iOS Safari、Firefox、Chrome、Chrome のすべてで読み込みバーが機能していることを示すデバッグコーナーのスクリーンショット。

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 つのブラウザすべてでアニメーション表示されるグラデーションが適用されます。

カスタム プロパティ

カスタム プロパティはさまざまな用途に役立ちますが、私のお気に入りは、魔法のように見える 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 の 10 進計算の問題を解決する

進行状況はデフォルトの最大値である 1 のままにするので、デモのインクリメント関数とデクリメント関数は 10 進計算を使用します。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 か所で使用されます。

  1. <progress> 要素の value 属性。
  2. aria-valuenow 属性。
  3. <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()
}

読み込みバーの進行状況をユーザーに読み上げる Mac OS の Voice Over アプリのスクリーンショット。

まとめ

私のやり方がわかったところで、どうしたらいいですか? 🙂?

もう一度チャンスがあれば、いくつか変更したいことがあります。現在のコンポーネントをクリーンアップする余地があり、<progress> 要素の擬似クラスのスタイル制限なしで構築する余地はあると思います。ぜひお試しください。

多様なアプローチを活用し、ウェブでアプリをビルドするためのあらゆる方法を学びましょう。

デモを作成してツイートのリンクをお願いします。下のコミュニティ リミックス セクションに追加します。

コミュニティのリミックス