进度环

进度环(或圆形进度条)可提供有关任务状态的视觉反馈。与直线进度条不同,圆形进度条非常适合用于信息中心、卡片组件或任何空间受限的位置。

本指南通过以下方式实现进度环:

  • 使用原生 <progress> 元素作为语义基础,确保屏幕阅读器和键盘用户开箱即用。
  • 使用 conic-gradient() 和 mask-image 为组件设置样式,从而实现完全自适应且可设置主题的圆环,而无需复杂的 SVG 路径操作。
  • 利用 CSS 自定义属性和 @property,实现进度填充的流畅、GPU 加速过渡。

此方法优于仅使用 SVG 的解决方案,因为它使用的是语义 <progress> 元素,而不是 ARIA,并且更容易与现有布局、设计体系和排版集成。

如需了解如何处理不确定的加载状态,请参阅加载旋转图标。

实现

1. Markup

使用封装容器来容纳视觉环和可选的中心内容。<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. 样式

隐藏原生界面

如需将 <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"],
  });
}