CSS 与 SVG 绘制圆环进度条的方法与缺陷

2,387 阅读3分钟

最近在一次移动端需求中,有需要实现圆环形状的进度条效果,其中遇到的一些绘制的圆环进度条在APP内表现非常坑的地方和大家分享一下

实现动态的圆环进度条效果有两种方式,使用 CSS 绘制和使用 SVG 绘制,两者各有优劣。

但除非是一些无法使用 SVG 的场景,最好使用更简单方便的 SVG 来绘制,尤其是在 APP 内的 H5 页面,0.5px问题会导致 CSS 绘制的表现无法达到预期效果,下面,一起看下具体的实现与表现结果

CSS 绘制圆环进度条

实现思路

首先,我们先来看下面这个旋转的静态二色圆环:

circle1.gif

在这个圆环里面,可以看到红色和橙色交替循环,第一眼看去并没有什么,但如果我们把它这个圆环的一半给盖住呢?我们分别来把左边和右边的一半盖住看看:

circle2.gifcircle3.gif

是不是刚好就是一个完整的圆环进度条的一半,把这两个圆组合起来,控制两者 tansform 角度,就是完整的圆环进度条。这就是 CSS 绘制圆环的实现思路,绘制两个不同的半圆,分别控制不同的百分比下的进度展示

border 绘制进度条

思路有了,实现起来就是采用不同的 CSS 样式来绘制圆环的区别了

最简单的就是使用 border来绘制圆环

使用 transition 来实现进度变换时的过渡动画

// 底色圆环
.circle-progress-border {
  position: absolute;
  width: 96px;
  height: 96px;
  border-radius: 50%;
  border: 8px solid $defaultColor;

  // 半圆显示框
  .wrapper {
    overflow: hidden;
    position: absolute;
    width: 48px;
    height: 96px;
    border-radius: 50%;
  }
  // 圆环统一样式
  .circle {
    width: 48px;
    height: 96px;
    border-radius: 50%;
    position: absolute;
  }

  // 左半圆
  .wrapper-left {
    left: 0;
  }
  .circle-left {
    left: 0;
    transform-origin: center;
    border: 8px solid transparent;
    border-bottom: 8px solid $runningColor;
    border-right: 8px solid $runningColor;
  }
  // 右半圆
  .wrapper-right {
    right: 0;
  }
  .circle-right {
    right: 0;
    transform-origin: center;
    border: 8px solid transparent;
    border-top: 8px solid $runningColor;
    border-left: 8px solid $runningColor;
  }
}
<div className="circle-progress-border">
  <div className="wrapper wrapper-left">
    <div className="circle circle-left" style={{ transform: "rotate(Xdeg)", transition: "all 1s" }} />
  </div>
  <div className="wrapper wrapper-right">
    <div className="circle circle-right" style={{ transform: "rotate(Xdeg)", transition: "all 1s" }} />
  </div>
</div>

circle1.png

Border 绘制的缺陷

Border 虽然说能够绘制出圆环,但这样的绘制在某些会将 px 转化 rem 的 H5 项目中,会因为 rem 出现 0.5px 的问题,导致 transform 出现偏移,图中标识的位置会出现偏移,导致进度条(橙色圆环)无法完全覆盖底色 (红色圆环)

为了解决这个问题,可以使用 Div 或 SVG 来绘制圆环

个人只在某些机型,APP 内打开 H5 页面的时候发现有这个问题

circle2.png

Div 绘制进度条

使用 Div 绘制圆环进度条,思路上与 border 是一致的,代码也非常相似,都是绘制两个半圆,然后使用 transform 控制旋转的角度以实现进度百分比效果

只是 Div 是使用 background 来绘制两个拥有橙色和红色的半圆,组成一个外圆,再在中心位置覆盖一个圆,由中心圆与外层圆的来组合生成圆环,在 transform 外层的两个半圆,实现进度条行进效果

.circle-progress {
  // 外环圆
  width: 96px;
  height: 96px;
  border-radius: 50%;
  position: absolute;

  ... .circle {
    width: 48px;
    height: 96px;
    border-radius: 50%;
    background-position: 0 0, 100% 0, 100% 100%, 0 100%;
    background-repeat: no-repeat, no-repeat;
  }

  .circle-left {
    left: 0;
    transform-origin: center;
    background-size: 50% 100%, 50% 100%;
    background-image: linear-gradient($defaultColor, $defaultColor), linear-gradient($runningColor, $runningColor);
  }
  .circle-right {
    right: 0;
    transform-origin: center;
    background-size: 50% 100%, 50% 100%;
    background-image: linear-gradient($runningColor, $runningColor), linear-gradient($defaultColor, $defaultColor);
  }

  // 中心圆
  .circle-center {
    width: 80px;
    height: 80px;
    border-radius: 50%;
    background: linear-gradient(180deg, rgba(255, 242, 194, 1) 0%, rgba(255, 173, 36, 1) 100%);
  }
}
<div className="circle-progress">
  <div className="wrapper wrapper-left">
    <div className="circle circle-left" style={{ transform: "rotate(Xdeg)", transition: "all 1s" }} />
  </div>
  <div className="wrapper wrapper-right">
    <div className="circle circle-right" style={{ transform: "rotate(Xdeg)", transition: "all 1s" }} />
  </div>
  <div className="circle-center" />
</div>

左:外圆 右:外圆+中心圆

circle7.pngcircle3.png

Div 绘制的效果与缺陷

一旦遇到前面说的 border 绘制方法的偏移的问题,Div 绘制的圆虽然不会出现色差,但如果圆比较小,可能会出现下方这种比较明显的 上环宽( 中心圆不居中 ) 的情况,这种情况一般很少出现,而且浏览器基本不会出现这个问题

circle8.png

使用 clip 简化圆环进度条绘制样式

上方设置一个宽度只有一半的外层 Div.wrapper,可以使用 Clip 属性来替换,通设置下列属性值来裁剪生成半圆,可以简化写法:

clip: rect(0 48px 96px 0);

使用 SVG 更简便的绘制圆环进度条

现阶段,使用 SVG 来绘制半圆可以说是最简单也是最不容易出错的一种写法

使用 SVG 可以在绘制一个圆环后,使用 strokeDasharraystrokeDashoffset 属性直接模拟进度条效果,更符合我们的认知,不需要其他的奇思妙想

使用 strokeDasharray 设置 偏移的起点实线长,使用 strokeDashoffset 设置偏移,实线进度条百分比的效果,变更 strokeDashoffset 的值,就可以实现进度条行进了,再加上一个底色背景圆环,就是一个完整的圆环进度条

但一些老版的浏览器(IE),并不支持 SVG,所以在用到 SVG 的时候还需要衡量一下使用场景

// d 周长 x 进度条百分比
<svg width="96px" height="96px">
  <circle id="circleBg" className="svg-circle-bg" />
  <circle className="svg-circle-running" strokeDasharray={d} strokeDashoffset={d - x} />
</svg>
.svg-circle-bg {
  r: 44px;
  stroke-width: 8;
  stroke: rgba(255, 161, 39, 1);
}
.svg-circle-running {
  r: 44px;
  stroke-width: 8;
  transition: all 1s linear;
  stroke: rgba(248, 31, 30, 1);
}

左:不加底色圆 右:加上底色圆

circle5.pngcircle6.png