结论:环形加载动画必须用 stroke-dashoffset 驱动,stroke-dasharray 固定为周长(如"314,314"),配合 rotate(-90deg) 起点对齐顶部;动态进度需 js 计算 offset = circumference × (1 - progress),并用 transition 或 animation 控制偏移变化。

直接说结论:用 stroke-dasharray + stroke-dashoffset + @keyframes 能做出基础环形加载动画,但仅适用于固定时长、无需响应数据变化的场景(比如页面 loading 闪三秒);一旦要动态显示 37% 或从 20% 变到 85%,就必须切到 JS 控制 stroke-dashoffset 内联值,CSS 只管过渡。
为什么只改 stroke-dasharray 没动画
常见错误是以为把 stroke-dasharray 从 0 动画到周长就能“画出来”,结果只闪一下或根本不动。本质错在:SVG 描边动画靠的是 stroke-dashoffset 的偏移,不是 dasharray 的伸缩。
-
stroke-dasharray必须固定为圆周长(如314),且第二个值建议显式写成相同值("314,314"),避免浏览器自动补全出意外虚线 -
stroke-dashoffset才是动画主角:初始设为314(整圈被“盖住”),终点设为0(整圈“露出”) - 如果用
@keyframes,必须配animation-fill-mode: forwards,否则动画一结束就回退到初始状态
transform: rotate(-90deg) 不加会怎样
不加就是从 3 点钟方向开始动——人眼第一反应是“怎么从右边开始绕”,尤其在移动端会明显违和。这不是 bug,是 SVG 坐标系默认行为。
-
<circle cx="50" cy="50" r="45"></circle>的起点永远在 0°(即正右方) -
transform="rotate(-90)"把整个圆逆时针转 90°,让起点落到顶部(12 点钟) - 这个 transform 必须写在
<circle></circle>标签里,不能只靠 CSS 类——否则动画过程中旋转和描边不同步 - 如果同时用 transition 控制
stroke-dashoffset和transform,两个属性得一起加进transition声明,否则有撕裂感
JS 动态更新进度时,stroke-dashoffset 怎么算
百分比转偏移量的公式很直接:offset = circumference * (1 - progress),但容易踩三个坑:
- 周长别手算:用
circle.getTotalLength()获取真实渲染周长,缩放、viewBox、preserveAspectRatio都不影响它 - 别等 DOM 渲染完立刻调
getTotalLength()——返回0;加个requestAnimationFrame延迟一帧再取 - JS 设置时用驼峰名:
circle.style.strokeDashoffset = offset + 'px',不是stroke-dashoffset - CSS 里必须提前声明
transition: stroke-dashoffset 0.3s ease,否则每次赋值都是突变
移动端卡顿或跳变的真正原因
不是代码写错了,而是没触发硬件加速。Android WebView 和旧版 iOS Safari 对纯描边动画优化差,尤其在低帧率设备上。
- 加
style="will-change: stroke-dashoffset"或transform: translateZ(0)强制 GPU 加速 - 别用
%单位写stroke-dasharray(如"100%")——SVG 属性只认数值,写死会失效 - 动画时间短(
0.3s)时,用ease-out比linear更自然;但进度突变大(比如 10% → 90%)时,linear反而更准,避免缓动带来的“拖尾感”
最常被忽略的一点:多个环形进度条共存时,每个 <circle></circle> 的 r 值不同,周长就不同,不能共用一套 CSS 动画变量或硬编码的 314;要么 JS 分别取 getTotalLength(),要么用 CSS 自定义属性传入各自周长,否则一个准、其余全漂。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











