最近在一次移动端需求中,有需要实现圆环形状的进度条效果,其中遇到的一些绘制的圆环进度条在APP内
表现非常坑的地方和大家分享一下
实现动态的圆环进度条效果有两种方式,使用 CSS
绘制和使用 SVG
绘制,两者各有优劣。
但除非是一些无法使用 SVG
的场景,最好使用更简单方便的 SVG
来绘制,尤其是在 APP 内的 H5 页面,0.5px问题
会导致 CSS 绘制的表现无法达到预期效果,下面,一起看下具体的实现与表现结果
CSS 绘制圆环进度条
实现思路
首先,我们先来看下面这个旋转的静态二色圆环:
在这个圆环里面,可以看到红色和橙色交替循环,第一眼看去并没有什么,但如果我们把它这个圆环的一半给盖住呢?我们分别来把左边和右边的一半盖住看看:
是不是刚好就是一个完整的圆环进度条的一半,把这两个圆组合起来,控制两者 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>
Border
绘制的缺陷
Border 虽然说能够绘制出圆环,但这样的绘制在某些会将 px 转化 rem 的 H5 项目中,会因为 rem 出现 0.5px 的问题,导致 transform 出现偏移,图中标识的位置会出现偏移,导致进度条(橙色圆环)无法完全覆盖底色 (红色圆环)
为了解决这个问题,可以使用 Div 或 SVG 来绘制圆环
个人只在某些机型,APP 内打开 H5 页面的时候发现有这个问题
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>
左:外圆 右:外圆+中心圆
Div 绘制的效果与缺陷
一旦遇到前面说的 border
绘制方法的偏移的问题,Div
绘制的圆虽然不会出现色差,但如果圆比较小,可能会出现下方这种比较明显的 上环宽( 中心圆不居中 ) 的情况,这种情况一般很少出现,而且浏览器基本不会出现这个问题
使用 clip 简化圆环进度条绘制样式
上方设置一个宽度只有一半的外层 Div.wrapper
,可以使用 Clip 属性来替换,通设置下列属性值来裁剪生成半圆,可以简化写法:
clip: rect(0 48px 96px 0);
使用 SVG 更简便的绘制圆环进度条
现阶段,使用 SVG 来绘制半圆可以说是最简单也是最不容易出错的一种写法
使用 SVG 可以在绘制一个圆环后,使用 strokeDasharray
、strokeDashoffset
属性直接模拟进度条效果,更符合我们的认知,不需要其他的奇思妙想
使用 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);
}
左:不加底色圆 右:加上底色圆