用SVG+CSS实现环状 进度条

核心原理:

利用两个重叠的圆环形,通过对上层圆环弧长的控制来表示进度,下层圆环则作为辅助,呈现环形进度条剩余的部分。

核心知识点:

  • SVG circle stroke-dasharray
  • 弧长公式 l = πrα/180°
  • CSS 变量
  • CSS 计数器

下面分享下具体实现过程。

实现环形

要实现环形,有多种技术可供选择,包括 Canvas、SVG,甚至 CSS + HTML 的组合。在本文中,我们使用了 SVG 方案,一来是因为 SVG 的 API 丰富,图形表现力非常强大,二来是 SVG 可以与 CSS 无缝搭配使用,实现更加强大的功能。

<svg width="200" height="200">
  <circle 
    cx="100" 
    cy="100"
    r="95"
    fill="none" 
    stroke="blue" 
    stroke-width="10" />
</svg>

小技巧:为了完美适应容器尺寸,我们可以将半径 r 的值设置为容器宽度的一半减去 stroke-width 大小的一半,这样做可以确保圆环不会因为溢出而被容器剪裁掉。此处 r = 200 / 2 - 10 / 2,即 (200 - 10) / 2 = 95

接着使用相同的方法绘制另一个圆环,作为辅助圆环。为了视觉效果,进度圆环应该在上层,因此在代码中,进度圆环的标签应该放在辅助圆环的后面。完成后的代码如下:

<svg width="200" height="200">
  <!-- 辅助圆环 -->
  <circle 
    cx="100" 
    cy="100" 
    r="95" 
    fill="none" 
    stroke="#ccc" 
    stroke-width="10" />
  
  <!-- 进度圆环 -->
  <circle 
    cx="100" 
    cy="100" 
    r="95" 
    fill="none" 
    stroke="blue" 
    stroke-width="10" />
</svg>

 可以发现代码中有很多重复的属性,为了让代码更加简洁和高效,我们可以使用 CSS 将这些重复的部分提取出来,统一声明,让代码变得更加“干”(DRY):

.progress-circle {
  width: 200px;
  height: 200px;
}

.progress-circle > circle {
  cx: 100px;
  cy: 100px;
  r: 95px;
  fill: none;
  stroke-width: 10px;
}
<svg class="progress-circle">
  <circle stroke="#ccc" />
  <circle stroke="blue" />
</svg>

实现圆环进度

上下两层的圆环已经准备好了,现在的重点是如何实现上层圆环上的进度,这个问题可以分为2个关键点:

  1. 如何实现圆环的弧长?
  2. 如何将进度百分比转换为圆环的弧长?

要解决第1个问题,这里要用到 SVG 中的 stroke-dasharray 属性,这是一个用来控制路径虚线疏密程度的属性,其值是一组描述虚线的短划线与空白间隙长度的数列。例如,如果设置 stroke-dasharray="5 2",则路径将以 5 个像素的短划线和 2 个像素的空白间隙交替显示,其中第一个数控制短划线长度,第二个数控制空白间隙长度。

stroke-dasharray 的参数值还支持多个数列,详情见  MDN 文档

很明显,这里我们需要控制圆弧的长度(即虚线中的短划线长度)来呈现进度。然而,由于虚线中的短划线是多个且重复的,仅仅改变短划线长度并不能满足我们的需求,具体情况如下图所示:

动图封面

根据需求,我们只需要圆环的 一段 圆弧即可,那如何实现呢?经过多次尝试,我们发现当改变虚线中空白间隙的长度(即 stroke-dasharray 的第二个数),当这个长度超过圆环的周长时,视觉效果上圆环只剩下一条独立的圆弧了,此时我们可以通过调整第一个数来改变圆弧的长度,从而解决了上面第1个问题。

动图封面

上面第2个问题暂时看起来比较棘手,因为我们很难看出0%~100%之间的进度到底对应多长的弧长。我们继续探究,寻找规律。

根据需求,当进度百分比是100%时,进度条的圆弧要呈现出一个圆环,此时圆弧夹角是360°,当进度百分比是50%时,圆弧是个半圆环,夹角是180° 。反过来也成立:180°表示50%,360°表示100% 。可以看出进度百分比和角度是存在等量关系的,同时根据弧长公式 l = πrα/180°,带入角度就可以求出弧长了,至此“进度百分比 - 角度 - 弧长”三者的规律就清晰了,思路马上要通了。

l = πrα/180°
其中  l 表示弧长, π 是圆周率, r 表示半径, α 表示夹角

动图封面

在实际使用中,我们是用百分数来控制弧长的,而不是用角度,所以接下来把进度百分比和弧长关联起来。

我们把弧长公式变动下,分子分母同时乘以2,则有:

l = 2πrα/180°*2,即 l = 2πr * α/360°

同时根据前面可知进度百分比和角度存在等量关系(360° 等于 100%),所以可以得到:

l = 2πr * p/100,其中 p 为当前进度百分数。

现在,我们就可以根据进度百分比来计算出对应的弧长了。

动图封面

优化细节

环形进度条通常都是从12点钟方向开始的,而 SVG 中默认是3点钟方向作为起点,所以我们给它偏转-90°进行修正:

.progress-circle {
  ...
  transform: rotate(-90deg);
}

另外我们发现圆弧的端点处过于生硬,给个圆角效果修饰下是个好主意。这里我们用了 SVG 中的属性 stroke-linecap

.progress-circle > circle {
  ...
  stroke-linecap: round;
}

当然了,动画过渡效果也可以安排上,让进度条的变化更加丝滑:

.progress-circle > circle {
  ...
  transition: stroke-dasharray 0.4s linear, stroke .3s;
}

组件化

环形进度条的效果虽然已经实现了,但光靠上面的那些代码还是很难复用。环形进度条的宽度、高度、半径、颜色等都是写死的,另外 stroke-dasharray 的值还得靠 JS 进行计算,再赋值给 <circle> 元素。说好的纯 CSS 实现呢?

要解决这些问题,我们需要将环形进度条组件化。

先定义一些组件全局要用到的 CSS 变量:

然后利用 calc 将写死的数值改为根据 CSS 变量动态计算:

.progressWrap{
	width: 120px;
	height: 120px;
	position: relative;
}
.progressWrap {
	--percent: 0;  /* 百分数 */
	--size: 120px;  /* 尺寸大小 */
	--border-width: 6px;  /* 环形宽度(粗细) */
	--color: #1E62EC;  /* 主色 */
	--inactive-color: #F5F7FA;  /* 辅助色 */
}
/* 容器 */
.progress-circle {
	width: var(--size);
	height: var(--size);
	transform: rotate(-90deg);
	border-radius: 50%;
}

/* 进度条环形图形 */
.progress-circle > circle {
	cx: calc(var(--size) / 2);
	cy: calc(var(--size) / 2);
	r: calc((var(--size) - var(--border-width)) / 2);
	fill: none;
	stroke-width: var(--border-width);
	stroke-linecap: round;
	transition: stroke-dasharray 0.4s linear, stroke .3s;
}
/* 百分数文本 */
.progressWrap::before {
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	counter-reset: progress var(--percent);
	content: counter(progress) '%';
	white-space: nowrap;
	font-size: 18px;
}

SVG 中的 stroke-dasharray 也调整为根据 CSS 变量动态计算: 

<div class="progressWrap">
	<svg class="progress-circle">
		 <circle stroke="var(--inactive-color)" />
		<circle stroke="var(--color)"
			    style="stroke-dasharray: calc( 2 * 3.1415 * (var(--size) - var(--border-width)) / 2 * (var(--percent) / 100)), 1000" />
	</svg>
</div>

这里 stroke-dasharray 第二个数值被设为了1000是为了虚线第二段出现的远一点 也就是想让它看不到。

这样我们通过改变父容器的 --percent 变量就能直接控制进度条的百分比显示了。

注意:引用的jquery最好是高一点的版本否则不支持css变量赋值

 $('.progressWrap').css({'--percent':20});		 

同理,也可以改变其他内部变量来方便地控制组件的外观和尺寸。例如可以根据阈值来动态调整进度条颜色:

function changeProgress(percent) {
  progressEl.style.setProperty('--percent', percent);

  [
    { value: 90, color: '#7c5' },
    { value: 70, color: '#65c' },
    { value: 50, color: '#fc3' },
    { value: 0, color: '#f66' }
  ].find(it => {
    if (percent >= it.value) {
      progressEl.style.setProperty('--color', it.color);
      return true;
    }
  });
}

参考文献:用CSS+SVG做一个优雅的环形进度条 - 知乎

好的,我可以为您提供一个 SVG 圆环进度条从 0 到 80% 的效果,进度条颜色为 #1e9fff。以下是代码示例: ```html <svg viewBox="0 0 100 100"> <circle cx="50" cy="50" r="40" fill="none" stroke="#ddd" stroke-width="20"></circle> <circle cx="50" cy="50" r="40" fill="none" stroke="#1e9fff" stroke-width="20" stroke-dasharray="251.2" stroke-dashoffset="251.2"> <animate attributeName="stroke-dashoffset" dur="1s" from="251.2" to="50.24" fill="freeze" begin="0s"></animate> </circle> </svg> ``` 解释一下代码: - `viewBox="0 0 100 100"` 定义 SVG 视口大小为 100 x 100。 - 第一个 `circle` 元素是灰色的底部圆环,它的 `cx` 和 `cy` 属性定义了圆心的位置,`r` 属性定义了半径,`stroke-width` 定义了圆环的线宽。 - 第二个 `circle` 元素是蓝色的进度圆环,它的 `stroke` 属性定义了圆环的颜色,`stroke-dasharray` 属性定义了虚线的样式,`stroke-dashoffset` 属性定义了起始偏移量。这个属性的值是底部圆环的周长(2 * π * r)。 - 在第二个 `circle` 元素中,我们使用了 `<animate>` 元素来定义动画效果。`attributeName` 属性定义了要进行动画的属性名,我们这里是 `stroke-dashoffset`。`dur` 属性定义了动画的持续时间,这里是 1 秒。`from` 属性定义了起始值,这里是底部圆环的周长。`to` 属性定义了结束值,这里是进度为 80% 时的周长(251.2)。`fill` 属性定义了动画结束后是否保持属性值,这里是 `freeze`。`begin` 属性定义了动画开始的时间,这里是 `0s`,表示从开始就执行动画。 以上代码可以在 HTML 文件中使用,也可以在 CSS 文件中作为 `background-image` 来使用。如果您需要不同的进度效果,可以修改 `stroke-dasharray` 和 `to` 属性的值即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值