SVG 配合 stroke-dasharray 和 stroke-dashoffset 是唯一真正可控的圆形旋转进度条实现方式,纯 CSS 方案在多浏览器中存在错位、撕裂、卡顿等问题,不适用于生产环境。

SVG <circle></circle> 配合 stroke-dasharray 和 stroke-dashoffset 是唯一真正可控的“圆形旋转进度条”实现方式。纯 CSS(比如用 border-radius + rotate + clip)在 Safari、旧版 Edge 和部分安卓 WebView 中会错位、撕裂或动画卡顿,不建议用于生产环境。
为什么不能只靠 CSS 的 transform: rotate()
有人试图用两个半圆遮罩+旋转模拟进度,但问题很实际:
- 小数角度(如 37.5°)在高 DPI 屏幕上渲染模糊,接缝明显
-
transform-origin偏移 1px 就会导致进度环“漂移”,尤其嵌套在 flex/grid 容器里 - CSS 动画 easing(如
cubic-bezier(0.4, 0, 0.2, 1))和真实进度曲线不匹配,用户感知“滞后”或“突跳” - 深色模式下,
clip-path边缘常出现灰边,且无法用prefers-color-scheme精准控制描边颜色
stroke-dasharray 的值必须动态算,不能硬编码
写死 stroke-dasharray="314" 看似省事,但一旦 SVG 缩放(比如用 width: 100%; height: auto 或 viewBox 配合 CSS 尺寸),实际周长就变了,进度立刻错位。
正确做法是用 JS 获取真实渲染长度:
立即学习“前端免费学习笔记(深入)”;
const circle = document.querySelector('.progress-circle');
// 必须等 SVG 渲染完成,否则 getTotalLength() 返回 0
if (circle.getTotalLength) {
const circumference = circle.getTotalLength();
circle.style.strokeDasharray = `${circumference}`;
circle.style.strokeDashoffset = circumference; // 初始隐藏
}
注意:别在 DOMContentLoaded 之前调用 getTotalLength();如果 SVG 是异步插入的,加一层 requestAnimationFrame 更稳。
让进度从顶部开始,只靠 transform="rotate(-90)" 不够
SVG 默认 0° 在正右方(3 点钟方向),rotate(-90) 能把起点转到顶部——但仅当 cx/cy 和 viewBox 对齐时才可靠。
更健壮的写法是用 <g transform="rotate(-90 50 50)"></g> 包住 <circle></circle>,并确保:
-
viewBox="0 0 100 100"(固定坐标系) -
cx="50" cy="50" r="45"(圆心居中,留出描边空间) -
stroke-linecap="round"(消除起点/终点接缝,避免“断点感”)
漏掉 stroke-linecap="round" 是最常被忽略的细节——尤其进度到 0% 或 100% 时,尖角会非常扎眼。
动画平滑的关键:只动 stroke-dashoffset,且用内联样式更新
CSS transition 在部分安卓 WebView 中对 stroke-dashoffset 不生效,直接写在 style 上最稳:
circle.style.transition = 'stroke-dashoffset 0.4s cubic-bezier(0.34, 1.56, 0.64, 1)'; circle.style.strokeDashoffset = circumference * (1 - progress / 100);
常见错误:
- 用
setAttribute('style', ...)覆盖全部样式,意外清掉transition - 在 CSS 里写
.progress-circle { transition: stroke-dashoffset ... },但某些浏览器不识别该属性名(得用stroke-dashoffset,不是strokeDashoffset) - 没设初始
stroke-dashoffset = circumference,导致动画第一帧“闪一下”
真正难的不是画圆,而是让 stroke-dashoffset 的数值变化和人眼感知的“旋转感”一致——这取决于 easing 函数选型和帧率控制,不是简单加个 ease-in-out 就行。



















