stroke-dasharray 和 stroke-dashoffset 是实现 svg 圆形进度环的核心,本质是通过描边虚线截取模拟“填充”,需配合 rotate(-90deg) 使起点在12点方向,周长必须动态计算并同步更新,css 动画仅驱动 stroke-dashoffset 以避免 safari 卡顿,同时须添加 aria 属性保障无障碍。

为什么 stroke-dasharray 和 stroke-dashoffset 是核心
SVG 圆形进度环的“填充”本质是描边的视觉截取,不是真正改变颜色区域。直接用 fill 无法实现从 0% 到 100% 的环形展开效果。必须借助 stroke-dasharray 定义虚线长度(设为周长),再用 stroke-dashoffset 控制起点偏移量——偏移量从周长减到 0,就看起来像在顺时针“画圈”。
常见错误:把 stroke-dasharray 设成固定值(如 100),没和圆的实际周长对齐,导致动画卡顿或不完整;或忘记重置 transform: rotate(-90deg),结果动画从 3 点钟方向开始,不符合常规“12 点钟起始”的直觉。
- 计算周长必须用
r * 2 * Math.PI,不能硬编码;响应式场景下,r应基于容器尺寸动态设定(如用vw或 JS 读取) -
stroke-dashoffset初始值 = 周长,结束值 = 0;CSS 动画中需用calc()或 JS 设置,纯 CSS 无法动态算周长,所以通常配合内联 style 或 JS 更新 - 务必加
transform: rotate(-90deg),否则 0% 时“空环”朝右,不是朝上
如何用 CSS 动画驱动,但避免 Safari 卡顿
CSS @keyframes 驱动 stroke-dashoffset 最简洁,但 Safari 对 SVG 属性动画支持不稳定,尤其在 transform + dashoffset 同时变化时易掉帧。稳妥做法是只动画 stroke-dashoffset,把旋转提前写死在 SVG 元素上。
示例结构:
PigX UI Pro 前端开发指南 - Vue 3 + TypeScript + Element Plus。当用户提到 PigX UI、PigX 前端、lgb-mgui 项目、Vue 3 企业级后台开发、Element Plus 后台开发时使用此技能。
<svg viewbox="0 0 100 100"><circle cx="50" cy="50" r="45" stroke="#e0e0e0" stroke-width="8" fill="none"></circle><circle cx="50" cy="50" r="45" stroke="#4caf50" stroke-width="8" fill="none" stroke-dasharray="282.74" stroke-dashoffset="282.74" transform="rotate(-90 50 50)" class="progress-ring__circle"></circle></svg>
- 周长 282.74 =
45 * 2 * 3.1416,保留两位小数足够,过多位数无意义 - 动画类只控制
stroke-dashoffset:`.progress-ring__circle.active { stroke-dashoffset: 0; transition: stroke-dashoffset 0.8s ease-in-out; }` - 不要给
<circle></circle>加transition外的其他动画属性(如opacity),Safari 可能触发重排
响应式适配:用 CSS 自定义属性 + JS 监听 resize 更可靠
纯 CSS 无法根据容器宽高实时重算 SVG 周长,vw 单位虽可缩放,但 r 改变后周长必须同步更新 stroke-dasharray 和初始 stroke-dashoffset,这一步只能靠 JS。
关键点:
- 把半径
r提取为 CSS 自定义属性:style="--r: 45;",SVG 中r="var(--r)" - JS 里监听
resize,重新计算周长:const circumference = 2 * Math.PI * radius;,然后用el.style.strokeDasharray = circumference和el.style.strokeDashoffset = circumference - 首次渲染和 resize 后,都需重置动画:先移除 active 类,
void el.offsetWidth强制重排,再加回 active 类 - 避免高频触发:用
requestAnimationFrame节流 resize 回调
进度值传入与无障碍支持容易被忽略
仅靠动画无法反映真实进度数值,屏幕阅读器看不到 stroke-dashoffset 的语义。必须显式暴露当前百分比。
- 给外层容器加
role="progressbar",并绑定aria-valuenow、aria-valuemin、aria-valuemax - 用 JS 同步更新这些属性值,例如:
el.setAttribute('aria-valuenow', percent) - 不要依赖 CSS 伪元素显示数字(如
::after),它默认不被读屏器识别;数字应作为真实文本节点插入,或用aria-label包含 - 动画完成时,确保
stroke-dashoffset精确为 0,浮点误差会导致最后 0.1% 卡住,可用Math.round()截断
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










