最可控的环形进度条实现是用 SVG 的 配合动态计算 stroke-dasharray 和 stroke-dashoffset,结合 viewBox 和 requestAnimationFrame 确保跨设备角度一致、缩放清晰、动画平滑。

用 + 手绘环形进度条最可控
纯 CSS 实现环形进度条容易在旋转中心、描边对齐、动画起始角度上出问题,尤其 IE 或旧版 Safari 下表现不一致。直接操作 SVG 的 属性(r、stroke-dasharray、stroke-dashoffset)能精准控制弧长和方向,兼容性也好。
关键点:
-
cx/cy设为圆心(如50),r决定半径(如45),确保 viewBox="0 0 100 100" - 周长 =
2 * Math.PI * r,设为stroke-dasharray值(如283),这样整圈就是满值 - 进度 =
stroke-dashoffset = 周长 * (1 - percent);percent是 0~1 小数 - 加
transform="rotate(-90 50 50)"让 0% 从顶部开始,符合直觉
stroke-dashoffset 为什么必须动态计算而不是写死
写死 stroke-dashoffset 值(比如直接设 141.5 表示 50%)看似省事,但一旦 r 改了、或需要响应式缩放,进度就错位。更糟的是,CSS 动画中它不会随 stroke-dasharray 自动重算。
推荐做法是用 JS 动态设置:
立即学习“前端免费学习笔记(深入)”;
const circle = document.querySelector('.progress-ring__circle');
const radius = 45;
const circumference = 2 * Math.PI * radius;
circle.style.strokeDasharray = `${circumference}`;
circle.style.strokeDashoffset = circumference - (percent * circumference);注意:别漏掉减号 —— stroke-dashoffset 是“向后偏移”,值越小显示越长。
用 requestAnimationFrame 替代 setTimeout 做平滑动画
直接用 setTimeout + 多次 setAttribute 容易卡顿,尤其低端设备。环形进度条常用于加载反馈,视觉连贯性很重要。
更稳的写法:
- 用
requestAnimationFrame控制帧率,避免丢帧 - 把目标进度转成当前帧要走到的
percent值,再算stroke-dashoffset - 加个 easing 函数(如
cubic-bezier(0.34, 1.56, 0.64, 1))让启动/结束更自然
不要直接在 CSS 里写 transition: stroke-dashoffset 0.4s ease-out —— SVG 的 stroke-dashoffset 在部分安卓 WebView 中过渡不生效。
移动端适配时 viewBox 比 width/height 更可靠
如果用固定像素宽高(如 width="200px"),在小屏上会挤占空间或触发横向滚动。而设 viewBox="0 0 100 100" + CSS 控制容器尺寸(如 width: 80vw; height: 80vw;),SVG 会自动等比缩放,且 cx/cy/r 仍用相对值,逻辑不变。
容易忽略的一点:preserveAspectRatio="xMidYMid meet" 必须显式加上,否则某些 iOS 版本可能拉伸变形。
环形进度条真正难的不是画出来,而是让不同设备下起始角度一致、动画不跳变、缩放不糊边 —— 这些都得靠 viewBox + 动态 dasharray + requestAnimationFrame 三者配合才稳。










