載入旋轉圖示 (或活動指標) 可在確切時間長度未知時,通知使用者正在處理中。與進度環不同,旋轉圖示是「不確定」的,通常會使用動畫來表示活動。
本指南會透過下列方式實作進度環:
- 使用原生
<progress>元素做為語意基礎。省略value屬性後,瀏覽器會將其視為不確定進度列,確保輔助技術正確朗讀。 - 使用
conic-gradient()設定元件樣式,建立視覺「軌跡」,並使用background-clip: border-area將中心挖空成圓環。 - 使用 CSS 轉換有效率地為旋轉圖示加上動畫,並遵循
prefers-reduced-motion,確保所有使用者都能享有舒適的體驗。
如要處理已知時間長度的確定工作,請參閱「進度環」。
導入作業
1. 標記
使用原生 <progress> 元素做為語意可靠資料來源和視覺元件。如果沒有 value 屬性,則會隱含不確定。
<progress aria-label="Loading" class="loading-spinner"></progress>
2. 樣式
隱藏原生 UI
如要將 <progress> 元素設為旋轉圖示,請先隱藏不確定進度列的預設瀏覽器樣式。
/* Hide native bars */
progress.loading-spinner:indeterminate::-webkit-progress-bar {
display: none;
background: none;
}
progress.loading-spinner:indeterminate::-webkit-progress-value {
display: none;
background: none;
}
progress.loading-spinner:indeterminate::-moz-progress-bar {
display: none;
background: none;
}
progress.loading-spinner:indeterminate::slider-fill {
display: none;
background: none;
}
旋轉環和軌跡
旋轉圖示會使用 conic-gradient 建立視覺軌跡。
@property --arc-start {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
@property --arc-end {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
/* Use a custom property to conditionally include the dash animation */
@property --progress-dash-animation {
syntax: "*";
inherits: false;
initial-value: , progress-dash 3s ease-in-out infinite;
}
progress.loading-spinner:indeterminate {
--_from: calc(90deg + var(--arc-start, 0deg));
--_to: calc(90deg + var(--arc-end, 158deg));
--size: 40px;
--thickness: 2px;
--spinner-color: #3b82f6;
--track-color: #e2e5e7;
--spinner-duration: 1.5s;
--_used-spinner-duration: var(--spinner-duration);
--spinner-timing: linear;
position: relative;
width: var(--size);
height: var(--size);
border-radius: 50%;
appearance: none;
/* Create the fading trail with dynamic angles */
background: conic-gradient(
from var(--_from),
var(--spinner-color) calc(var(--_to) - var(--_from)),
transparent 0
)
var(--track-color);
@supports (background-clip: border-area) {
background-clip: border-area;
border: var(--thickness) solid transparent;
background-origin: border-box;
}
/* ... fallback for background-clip: border-area ... */
/* The dash animation is only included if @property is supported */
animation:
progress-spin var(--_used-spinner-duration) linear infinite
var(--progress-dash-animation, );
}
@keyframes progress-spin {
to {
rotate: 1turn;
}
}
@keyframes progress-dash {
from {
--arc-start: 0deg;
--arc-end: 3deg;
}
50% {
--arc-start: 100deg;
--arc-end: 358deg;
}
to {
--arc-start: 360deg;
--arc-end: 363deg;
}
}
遵循動作偏好設定
對動態敏感的使用者可能會覺得快速旋轉的元素令人暈眩。請務必遵循 prefers-reduced-motion 媒體查詢。設定內部 --_used-spinner-duration 屬性,覆寫使用者的 --spinner-duration 值。
@media (prefers-reduced-motion: reduce) {
.loading-spinner {
/* Slow down the animation significantly rather than stopping it entirely,
so the user still knows that the process is active. */
--_used-spinner-duration: 6s;
}
}
備用策略
如果支援 @property,系統會透過 --progress-dash-animation 屬性的 initial-value 自動加入虛線動畫。在不支援 @property 的瀏覽器中,系統會忽略屬性註冊,動畫會改為簡單的旋轉。這個備用廣告不需要 JavaScript。
如果瀏覽器不支援 background-clip: border-area,請改用 mask-image 挖空中心。
/* Fallback: use mask-image to create the ring */
@supports not (background-clip: border-area) {
--clip-boundary: calc(100% - var(--thickness));
mask-image: radial-gradient(
farthest-side,
transparent var(--clip-boundary),
black var(--clip-boundary)
);
border: 0;
}