基本總覽:如何使用 <progress>
元素建構可自動調整顏色且可存取的載入列。
在這篇文章中,我想分享一些想法,說明如何運用 <progress>
元素建構可自動調整顏色且方便存取的載入列。歡迎試用示範功能並查看原始碼!
如果您喜歡看影片,請參考這篇文章的 YouTube 版本:
總覽
<progress>
元素提供視覺和音訊回饋,讓使用者瞭解完成設計。這種視覺回饋適用於各種情境,例如透過表單遊玩、顯示下載或上傳資訊,或甚至是顯示進度金額不明,但作業仍在進行中。
這項 GUI 挑戰可與現有的 HTML <progress>
元素搭配運作,節省一些無障礙功能的工作。色彩和版面配置會推動內建元素自訂限制,以翻新元件,讓元件更符合設計系統。
![在每個瀏覽器的淺色和深色分頁中,您可以全盤瞭解從上到下的自動調整圖示:Safari、Firefox、Chrome。](https://web.dev/static/articles/building/a-loading-bar-component/image/light-dark-tabs-each-br-cd04389a437d1.png?authuser=6&hl=zh-tw)
標記
我選擇將 <progress>
元素納入 <label>
,以便略過明確關係屬性,改用隱含關係。我也為受到載入狀態影響的父項元素加上標籤,以便螢幕閱讀器技術將這些資訊轉發給使用者。
<progress></progress>
如果沒有 value
,表示元素的進度「不確定」。max
屬性預設為 1,因此進度介於 0 到 1 之間。舉例來說,將 max
設為 100 會將範圍設為 0 到 100。我選擇將進度值轉譯為 0.5 或 50% 以不超過 0 和 1 的限制。
標籤包裝進度
在隱含關係中,進度元素會用標籤包裝,如下所示:
<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>
版面配置
進度元素的寬度維持不變,方便配合設計所需的空間縮小及變大。將 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: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);
}
顏色
瀏覽器會為進度元素顯示專屬顏色,而且只使用一個 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 開發人員工具會顯示下列元素:
- 在頁面上按一下滑鼠右鍵,然後選取「檢查元素」開啟開發人員工具。
- 按一下開發人員工具視窗右上角的「設定」齒輪。
- 在「Elements」標題下方,找出並啟用「Show user agent 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 屬性組合。
以動畫呈現音軌填滿
在 Chrome 上新增進度元素的 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
在此用法中,請移除 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 中的三個位置:
<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>
元素虛擬類別樣式限制的情況下,建構一個元件。值得一探究竟!
讓我們來體驗多元的方法,瞭解透過網路建立內容的所有方式。
建立示範、張貼推文 連結,以便我們將其新增至下方的社群重混專區!