进度环(或圆形进度条)可提供有关任务状态的视觉反馈。与直线进度条不同,圆形进度条非常适合用于信息中心、卡片组件或任何空间受限的位置。
本指南通过以下方式实现进度环:
- 使用原生
<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"],
});
}