svg + stroke-dasharray 是最可控的环形进度条方案,通过周长计算、stroke-dashoffset 动态控制进度,配合 stroke-linecap="round" 消除接缝,支持高精度缩放与平滑动画。

用 SVG <circle></circle> + stroke-dasharray 实现最可控的环形进度条
纯 CSS 方案(比如用 border-radius: 50% 配合旋转遮罩)看起来简单,但实际很难精确控制进度、响应式缩放易错、动画卡顿常见。SVG 是目前最稳妥的选择,核心靠 <circle></circle> 的描边路径长度和虚线绘制逻辑来模拟“画圆”过程。
关键点在于:先算出圆周长 2 * π * r,再用 stroke-dasharray 设定“实线段长度 = 当前进度 × 周长”,stroke-dashoffset 控制起始绘制位置(通常设为负值让进度从顶部开始)。
示例(r=50,viewBox="0 0 100 100"):
<svg viewbox="0 0 100 100"><circle cx="50" cy="50" r="45" fill="none" stroke="#e0e0e0" stroke-width="8"></circle><circle cx="50" cy="50" r="45" fill="none" stroke="#4CAF50" stroke-width="8" stroke-dasharray="282.74">
stroke-dashoffset="282.74" <!-- 初始全空 -->
style="transition: stroke-dashoffset 0.3s ease-out;" />
</circle></svg>
JS 更新进度时只需动态改 stroke-dashoffset:
const progressCircle = document.querySelector('svg circle:last-child');
const circumference = 282.74;
const targetPercent = 75;
progressCircle.style.strokeDashoffset = circumference - (targetPercent / 100) * circumference;
为什么不能直接用 transform: rotate() 模拟进度
有人尝试用一个半圆 <div> 旋转 + overflow 遮罩,或两个 180° 扇形拼接——这类方案在进度接近 0% 或 100% 时极易出现视觉撕裂、角度计算误差(尤其涉及小数精度)、CSS 动画 easing 不匹配真实进度曲线。<p>更麻烦的是:旋转中心偏移、父容器 transform 影响、高 DPI 屏幕下锯齿明显。而 SVG 路径绘制是矢量原生支持,缩放不失真,动画由浏览器渲染管线统一调度,稳定性高得多。</p>
<p>常见翻车点:</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill8272" title="anything-to-html"><img
src="https://img.php.cn/upload/skill/000/000/081/179195588450431.jpg" alt="anything-to-html" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill8272" title="anything-to-html" class="overflowclass">anything-to-html</a>
<p class="overflowclass">将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。</p>
</div>
<a rel="nofollow" href="/xiazai/skill8272" title="anything-to-html" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
<ul>
<li>用 <code>deg 计算旋转角度但没考虑起始方向(默认 0° 在右侧,环形进度习惯从顶部开始)
transform-origin: center 导致旋转轴心偏移
stroke-linecap: round 能解决接头毛刺问题
当进度不是整圆时,<circle></circle> 的起点和终点会暴露尖锐接缝,尤其在粗描边(stroke-width > 6)下非常明显。加一句 stroke-linecap="round" 就能让两端自动变成半圆帽,视觉上严丝合缝。
注意:这个属性只对描边生效,不影响填充;且必须加在带 stroke-dasharray 的那个 <circle></circle> 上,否则无效。
其他实用配置项:
-
stroke-linejoin="round":如果后续要扩展成多段弧(比如不同颜色进度),防止连接角出现棱角 -
shape-rendering="geometricPrecision":在需要高精度路径(如金融仪表盘)时启用,避免浏览器自动优化导致微小偏移 - 避免在
<circle></circle>上用fill,哪怕设为none,也比留空更稳妥(某些旧版 Safari 对空 fill 处理异常)
React/Vue 中封装要注意 props 变化时的动画重置
组件接收 percent prop 后,不能每次更新都直接设 stroke-dashoffset —— 如果新旧值相同,浏览器可能跳过重绘;如果值突变(比如从 10 → 80),需确保 transition 属性始终存在且未被内联样式覆盖。
推荐做法:
- 用
useEffect(React)或watch(Vue)监听percent,计算新offset后强制触发 reflow(例如读取getBoundingClientRect())再设值 - 把
transition写在 CSS 类里,而不是内联 style,避免被 JS 覆盖 - 初始渲染时若
percent === 0,stroke-dashoffset应设为最大值(即周长),而非 0 —— 否则会显示满圈
真正难的不是画出来,而是让 0.1% 的变化在 retina 屏上也平滑、不跳、不糊。SVG 路径方案在这点上容错率高,但每个数值(r、stroke-width、dasharray)之间都有隐含比例关系,动一个就得同步调另外两个。










