建立載入列元件

使用 <progress> 元素建構自動調整色彩且可存取的載入列的基本總覽。

在這篇文章中,我想分享如何運用 <progress> 元素建構可自動調整的色彩,以及容易存取的載入列。歡迎試用示範版查看原始碼

Chrome 已降低淺色和深色、未執行進度、增加和完成數。

如果你偏好使用影片,也可以觀看這篇 YouTube 文章:

總覽

<progress> 元素提供視覺和聽覺回饋,告知使用者播放完成情形。這項視覺回饋適用於各種情境,例如:填寫表單、顯示下載或上傳資訊,或甚至是顯示進度不明但仍在處理中。

這項 GUI 挑戰能與現有的 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;
}

開發人員工具的螢幕截圖,顯示可供螢幕使用的元素。

受載入進度影響的區域

如果您有健全的願景,就能輕鬆將進度指標與相關元素和頁面區域建立關聯。但對視障使用者來說,前景服務並不明確。如要改善此效果,請將 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>

使用 tabindex="-1" 讓進度元素可從 JavaScript 聚焦。這對螢幕閱讀器技術來說非常重要,因為在進度變更時,系統會向使用者通知進度的焦點,距離更新完成的進度。

風格

設定樣式時,進度元素會稍微複雜。內建 HTML 元素具有特殊的隱藏部分,不易選取,而且通常只會提供有限的屬性組合。

版面配置

版面配置樣式可讓您靈活調整進度元素的大小和標籤位置。系統會加入特殊的完成狀態,這個狀態雖然實用,但並非必要,可額外提供視覺提示。

<progress>」版面配置

進度元素的寬度不會改變,因此可以縮減並隨設計所需的空間增加。只要將 appearanceborder 設為 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;
}

_radius1e3px 值使用科學編號標記法表示較大的數字,因此 border-radius 會一律四捨五入。等同於 1000px。我希望使用這個範圍,因為我的目標是使用夠大的值,這樣我可以設定並忘記該值 (而且可以寫入的時間較短,而且可以寫入的時間也短)。如有需要,您也可以將其放大:只要將 3 變更為 4,然後 1e4px 相當於 10000px1000px

已使用 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% 的螢幕截圖,結尾處顯示勾號。

顏色

瀏覽器為進度元素提供專屬的色彩,且只需透過一個 CSS 屬性,即可自動調整為淺色和深色。您可以利用某些特殊的瀏覽器專用選取器建構這項功能。

淺色和深色瀏覽器樣式

如要為網站啟用深色及淺色自動調整 <progress> 元素,只需使用 color-scheme 即可。

progress {
  color-scheme: light dark;
}

單一屬性進度填滿顏色

如要為 <progress> 元素上色,請使用 accent-color

progress {
  accent-color: rebeccapurple;
}

請注意,軌跡背景顏色會從淺色到深色,視 accent-color 而定。瀏覽器可以確保對比度:忠實呈現。

完全自訂的淺色和深色

<progress> 元素上設定兩個自訂屬性,一個用於軌道顏色,另一個用於追蹤進度顏色。在 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 開發人員工具就會為您顯示這些元素:

  1. 在網頁上按一下滑鼠右鍵,然後選取「檢查元素」可開啟開發人員工具。
  2. 按一下開發人員工具視窗右上角的「設定」齒輪圖示。
  3. 在「Elements」(元素) 標題下方,找出並啟用「Show user agent shadow DOM」核取方塊。

螢幕截圖:在開發人員工具中,啟用公開使用者代理程式 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 的螢幕截圖,以及進度元素部分的位置。

偵錯角落的螢幕截圖,在 Safari、iOS Safari、Firefox、Chrome 和 Android 版 Chrome 中,載入列都會顯示載入列。

請注意,Firefox 的軌跡顏色是 accent-color,iOS Safari 則顯示淺藍色音軌。使用深色模式時,情況相同:Firefox 有深色軌,但沒有我們設定的自訂顏色,可以在 Webkit 式瀏覽器中使用。

動畫

使用瀏覽器內建虛擬選取器時,通常是受允許的 CSS 屬性組合有限。

為賽道填滿動畫

為進度元素的 inline-size 新增轉場效果適用於 Chromium,但 Safari 不適用。此外,Firefox 也不會在 ::-moz-progress-bar 上使用轉換屬性。

/*  Chromium Only 😢  */
progress[value]::-webkit-progress-value {
  background-color: var(--_progress);
  transition: inline-size .25s ease-out;
}

:indeterminate 狀態加上動畫效果

我現在要發揮創意 製作動畫。建立 Chromium 虛擬元素後,系統會針對這三個瀏覽器來回套用動畫效果。

自訂屬性

自訂屬性非常適合許多用途,但我們最喜歡的其中一項就是為 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 中設定。只需要一個主要畫面格 (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/使用者體驗自動化調度管理作業。請先建立 setProgress() 函式。不需要參數,因為它可以存取 state 物件、進度元素和 <main> 區域。

const setProgress = () => {
  
}

設定 <main> 區域的載入狀態

視進度是否完成而定,相關的 <main> 元素需要更新 aria-busy 屬性:

const setProgress = () => {
  zone.setAttribute('aria-busy', state.val < 1)
}

載入量不明時清除屬性

如果值不明或未設定,請移除此用法中的 null,請移除 valuearia-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 的三個位置:

  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> 元素的虛擬類別樣式限制,可以嘗試建構元件。值得一探究竟!

讓我們帶您更多元的方法,並瞭解運用網路打造網站的所有方式。

請建立示範並透過 Twitter 推文連結,我就能將這項工具新增至下方的「社群重混」部分!

社群重混作品