概略說明如何使用 <progress>
元素,建構可自動調整顏色且符合無障礙設計的載入列。
在這篇文章中,我想分享如何使用 <progress>
元素,建構可自適應色彩且符合無障礙設計的載入列。試用示範並查看來源!
如果你偏好觀看影片,請參閱這篇文章的 YouTube 版本:
總覽
<progress>
元素會向使用者提供關於完成作業的視覺和音訊回饋。這類視覺回饋在以下情況下非常實用:透過表單進行進度、顯示下載或上傳資訊,甚至顯示進度數量不明,但工作仍在進行中。
這個 GUI 挑戰使用現有的 HTML <progress>
元素,可節省一些無障礙功能的努力。顏色和版面配置可突破內建元素的自訂限制,讓元件更符合現代化需求,並更適合納入設計系統。
標記
我選擇將 <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>
版面配置
進度元素的寬度保持不變,因此可根據設計中所需的空間縮小或放大。將 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 開發人員工具會顯示下列元素:
- 在網頁上按一下滑鼠右鍵,然後選取「檢查元素」即可開啟開發人員工具。
- 按一下 DevTools 視窗右上角的「設定」齒輪圖示。
- 在「元素」標題下方,找出並啟用「顯示使用者代理程式陰影 DOM」核取方塊。
Safari 和 Chromium 樣式
以 WebKit 為基礎的瀏覽器 (例如 Safari 和 Chromium) 會公開 ::-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
上使用轉場屬性。
/* 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/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 中的三個位置:
<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>
元素的擬造類別樣式限制的元件。值得一試!
讓我們多方嘗試,瞭解在網路上建構應用程式的所有方式。
請製作示範作品,並在推特上傳連結,我會將其加入下方的社群重混曲目錄!