進度環

進度環 (或圓形進度列) 可提供工作狀態的視覺回饋。與線狀進度列不同,圓形進度列適合用於資訊主頁、資訊卡元件或任何空間有限的地方。

本指南會透過以下方式實作進度環:

  • 使用原生 <progress> 元素做為語意基礎,確保螢幕閱讀器和鍵盤使用者能直接使用該元件。
  • 使用 conic-gradient() 和 mask-image 設定元件樣式,即可建立完全回應式且可設定主題的圓環,不必進行複雜的 SVG 路徑操作。
  • 運用 CSS 自訂屬性和 @property,讓進度填滿效果流暢地進行 GPU 加速轉換。

這種做法比僅使用 SVG 的解決方案更理想,因為它使用語意 <progress> 元素而非 ARIA,且更容易與現有的版面配置、設計系統和字體排版整合。

如要處理不確定的載入狀態,請參閱載入旋轉圖示。

導入作業

1. 標記

使用包裝函式來保留視覺環和選用的中心內容。<progress> 元素仍是語意可靠資料來源。使用公用程式類別,以視覺方式隱藏原生進度列,同時保持可存取性。

<div class="ring-wrapper">
  <progress value="75" max="100" aria-label="Task progress" class="progress-ring"></progress>
  <!-- Optional: Content to display in the center -->
  <div class="ring-content">
    75%
  </div>
</div>

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

容器和環

包裝函式會提供定位情境。<progress> 元素會處理視覺漸層。

.progress-ring-wrapper {
  position: relative;
  display: grid;
  place-items: center;
}

progress.progress-ring {
  --size: 150px;
  --thickness: 16px;
  --track-color: #f1f5f9;
  --fill-color: #3b82f6;
  --value: attr(value type(<number>));
  width: var(--size);
  height: var(--size);

  transition: --value 0.4s cubic-bezier(0.4, 0, 0.2, 1);
  border-radius: 50%;

  background: conic-gradient(
    var(--fill-color) calc(var(--value) * 1%),
    var(--track-color) 0
  );

  /* MANDATORY: Clip the background to the border-area. */
  background-clip: border-area;
  border: var(--thickness) solid transparent;
  background-origin: border-box;
}

.progress-ring-content {
  /* Positioned in the center of the wrapper */
  position: absolute;
}

您也可以使用 radial-gradient 製作圓角端蓋。

使用 @property 啟用流暢轉場效果

如要在值變更時流暢地為進度環加上動畫效果,請將 --value 註冊為數值自訂屬性。

對動態敏感的使用者可能會覺得值之間的轉換令人困惑。預設持續時間為 0 秒 (立即),並為沒有偏好的使用者設定較長的時間,以遵循 prefers-reduced-motion 媒體查詢。

@property --value {
  syntax: '<number>';
  inherits: true;
  initial-value: 0;
}

progress.progress-ring {
  transition: --value 0s ease-in-out;
  @media (prefers-reduced-motion: no-preference) {
    transition-duration: 0.4s;
  }
}

3. 進度更新

每當值變更時,請更新 <progress> 元素的 value 屬性,以及 .progress-ring-content 的文字內容 (如果顯示百分比)。

4. 選用成功狀態

您可以使用 CSS 屬性選取器,在工作完成時自動更新圓環的外觀 (例如將顏色變更為綠色)。

/* Change the fill color to green when the progress reaches 100% */
progress.progress-ring[value="100"] {
  --fill-color: #10b981;
}

備用策略

請勿在 <progress> 元素中加入備用值。輔助技術不會使用這個屬性,所有新式瀏覽器也會忽略這個屬性。

動畫備用廣告

如果不支援 @property,圓環會立即跳至新值,而不會流暢轉換。在大多數情況下,這不會造成任何問題,也不會破壞任何功能。

如果絕對需要轉場效果,可以檢查是否支援 @property,並使用 requestAnimationFrame() 迴圈在舊版瀏覽器中內插 --value。

如果瀏覽器不支援 background-clip: border-area,請改用 mask-image 挖空 <progress> 元素的中心。

@supports not (background-clip: border-area) {
  mask-image: radial-gradient(
    transparent calc(50% - var(--thickness)),
    black calc(50% - var(--thickness) + 0.5px)
  );
  border: 0;
}

如果瀏覽器不支援任何屬性的 attr() CSS 函式,請使用 MutationObserver,將 value 屬性自動同步至 --value 自訂屬性。

if (!CSS.supports("width: attr(value type(<number>))")) {
  const observer = new MutationObserver(() => {
    progress.style.setProperty("--value", progress.getAttribute("value"));
  });
  observer.observe(progress, {
    attributes: true,
    attributeFilter: ["value"],
  });
}