一、前言
UI组件
系列好久没有更新了,今天我们来分析一下常见的一种组件:环形进度条
,也叫 圆形进度条
。这种组件在被使用的时候又显的吃力,因为UI设计师在设计它的时候往往会增加一些元素,来显的高大尚一些。这种时候鸡肋点就显示出来了:
- 往往
组件库
是不支持扩展的。 - 为了让它高大尚 去引入相应的专属第三方库又显的没必要。
- 圆形进度条组件在市面上有,但是很少有多端支持的。
所以为了解决上述痛点,我们不得不自己写一个这样的组件。如果你对它无从下手,或者思路不清晰,那么这篇文章正好适合你。
那就让我们一起let go 吧。
二、效果展示
10%的效果是这样的
:
25%的效果是这样的
:
50%的效果是这样的
:
75的效果是这样的
:
三、实现前的说明
本次实现的功能说明:
- 1、本篇文章使用
canvas
来绘画圆形进度条
组件。 - 2、基本的环形进度条展示。
- 3、定制环形进度条结尾处的元素。
万丈高楼平地起,我们本次文章主打的就是基础建设。只有基础建设的好,我们才能在此基础上做很多的扩展。
四、实现过程
4.1、准备画布
<style>
div {
width: 300px;
height: 300px;
}
canvas {
width: 100%;
height: 100%;
}
</style>
<div>
<canvas id="canvas"></canvas>
<script>
function drawerCircle (){
let canvas = document.getElementById('canvas');
let elementBox = document.querySelector('div');
canvas.width = elementBox.getBoundingClientRect().width;
canvas.height = elementBox.getBoundingClientRect().height;
}
</script>
</div>
好了,第一个问题来了,我们给canvas设置大小有3种方式:
1、像上面这样,通过js来设置。
2、直接给canvas添加100%的行内样式。<canvas width="100%" height="100%"></canvas>
3、在样式表里设置canvas标签样式。
我们为什么不采用第二种、第三种方式?知道答案的小伙伴可在评论区里回答一下。
4.2 如何画圆圈?
这里引用了菜鸟教程的定义。上面的图主要传递了以下几点信息:
sAngle、eAngle
传入的都是弧度
,而不是角度。counterclockwise
代表的是绘画方向。- 弧度与角度的关系:
角度 = ( 180 * 弧度 ) / Math.PI
。
<style>
div {
width: '300px';
height: '300px';
}
</style>
<div>
<canvas id='canvas'>当前浏览器不支持画布元素</canvas>
</div>
function drawerCicle(){
let canvas = document.getElementById('canvas');
canvas.width = document.querySelector('div')?.getBoundingClientRect()?.width;
canvas.height = document.querySelector('div')?.getBoundingClientRect()?.height;
// 1、确定组件的圆心位置
let xCenter = canvas.width / 2;
let yCenter = canvas.height / 2;
// 2、拿到绘画上下文
let ctx = canvas.getContext('2d');
// 3、开启绘画路径
ctx.beginPath();
// 4、确定画笔颜色
ctx.strokeStyle = '#ccc';
// 5、设置画笔粗细
ctx.lineWidth = 10;
// 6、确定圆弧半径
let radius = (canvas.width - ( 2 * ctx.lineWidth ) ) / 2;
// 7、调用arc绘制圆弧
ctx.arc(xCenter, yCenter, radius, 0, 2 * Math.PI, false);
// 8、将圆弧上的点连线
ctx.stroke();
// 9、关闭路径
ctx.closePath();
}
drawerCicle();
根据代码,我们已经可以画出来圆圈了,如下图:
4.3、在上一步的基础上,如何画进度?
其实就是重复第二步嘛,画2遍圆,然后重合一下就行了。我们先改动一点代码来验证我们的思路:
function drawerCicle(){
let canvas = document.getElementById('canvas');
canvas.width = document.querySelector('div')?.getBoundingClientRect()?.width;
canvas.height = document.querySelector('div')?.getBoundingClientRect()?.height;
// 1、确定组件的圆心位置
let xCenter = canvas.width / 2;
let yCenter = canvas.height / 2;
// 2、拿到绘画上下文
let ctx = canvas.getContext('2d');
// 3、开启绘画路径
ctx.beginPath();
// 4、确定画笔颜色
ctx.strokeStyle = '#ccc';
// 5、设置画笔粗细
ctx.lineWidth = 10;
// 6、确定圆弧半径
let radius = (canvas.width - ( 2 * ctx.lineWidth ) ) / 2;
// 7、调用arc绘制圆弧
ctx.arc(xCenter, yCenter, radius, 0, 2 * Math.PI, false);
// 8、将圆弧上的点连线
ctx.stroke();
// 9、关闭路径
ctx.closePath();
// 画进度弧 ===============================
ctx.beginPath();
// 4、确定画笔颜色
ctx.strokeStyle = 'orange';
// 5、设置画笔粗细
ctx.lineWidth = 10;
// 7、调用arc绘制圆弧
ctx.arc(xCenter, yCenter, radius, 0, 0.5 * Math.PI, false);
// 8、将圆弧上的点连线
ctx.stroke();
// 9、关闭路径
ctx.closePath();
}
此时的结果如下:
看来思路是对的,但就是有点瑕疵:
问题a: 我们的圆弧应该是从板块1开始,而不是从板块4开始
。
问题b: 如何确定圆弧长度?
。
4.3.1、问题a
从
4.2小结
我们知道,对于一个圆弧来说,在中心点已知的情况下,起始弧、终止弧、绘画方向
决定了圆弧的形状。
我们现在画圆弧的代码是这个:
ctx.arc(xCenter, yCenter, radius, 0, 0.5 * Math.PI, false);
// 对于这段代码的解释如下:
// 1、起始弧度0,根据弧度与角度的换算关系,得出起始角度为0度
// 2、终止弧度0.5 * Math.PI,根据弧度与角度的换算关系,得出终止角度90度
// 3、false代表顺时针绘画
所以,如果我们想要进度从第一板块开始,那么我们只需要调整 起始弧度
与 终止弧度
即可。
调整代码如下:
ctx.arc(xCenter, yCenter, radius, 1.5*Math.PI, 2 * Math.PI, false);
现在的样式如下:
4.3.2、问题b
如何确定进度弧长?我们可以通过传参来确定,规定传参是小数或者百分比
。
一个圆弧的总弧长是2*Math.PI
,so,2*Math.PI*传参
就是我们的进度弧长。
因为我们的起始弧长是 1.5*Math.PI
, so,我们的终止弧长就是:1.5*Math.PI + 2*Math.PI*传参
。
分析完毕,调整代码如下:
function drawerCicle(){
// 规定进度条占比
let percent = 0.1;
//画基础圆弧代码不变...
// 画进度圆弧
ctx.beginPath();
ctx.strokeStyle = 'orange';
ctx.lineWidth = 10;
ctx.arc(xCenter, yCenter, radius, 1.5*Math.PI, (1.5*Math.PI) + (percent * 2 * Math.PI), false);
ctx.stroke();
ctx.closePath();
}
现在的10%的现象如下:
4.4、在进度的结尾,如何画圆球?
这个功能的难点在于如何得到圆球的中心坐标
。在这里跟大家说个结论,(lineWidth = 12
上的圆弧里的点) === (lineWidth = 1
上的圆弧里的点)
计算圆球中心逻辑如下:
- 如果进度对应的角度落在
第1板块
, 说明此时进度圆弧对应的角度A
在(0, 90]区间。 - 如果进度对应的角度落在
第4板块
, 说明此时进度圆弧对应的角度A
在(90, 180]区间。 - 如果进度对应的角度落在
第3板块
, 说明此时进度圆弧对应的角度A
在(180, 270]区间。 - 如果进度对应的角度落在
第2板块
, 说明此时进度圆弧对应的角度A
在(270, 360]区间。
我们不需要所有的板块都分析,我们只分析 第1板块
,其他板块都类似。
第1板块
首先,我们需要求出进度对应的角度是多少。
这个很简单,从技术角度来看,进度占比percent
是用户自定义的,同时也是一个必传的属性。假定我们现在要画20%的进度,那么此时的传参percent
就是0.2。整个圆弧的角度360°,那么percent
对应的角度就应该是0.2 * 360°
其次,假设弧度对应的角度是30°,如下图:
这里再强调一下,注意到c
和d
中间的黑点了吗,黑点坐标其实就是c点坐标
。我们的圆球坐标就是黑点坐标。
也就是说如果我们以c
为中心画圆,那么最终的位置效果就是以黑点
为中心画圆。
根据上面的分析,我们可以写出如下函数来确定圆球的中心位置。
// 确定圆球的中心位置
/**
1、BigCircleRadius:蓝色圆的半径,也就是上图的 `oa`
2、Deg:进度圆弧对应的角度,也就是 `前文中的percent` * `360`
3、Angle:进度的终止弧,我们能得到角度,再根据`角度与弧度的换算关系`得出终止弧长。结果:(( percent * 360 ) / 180) * Math.PI
4、halfRect:蓝色正方形的width或者高度的一半,也就是上图中的 `ob`
5、返回值:进度弧上终点的坐标
*/
function surePosition(BigCircleRadius, Deg, Angle, halfRect){
let result = {
x: 0,
y: 0
}
if (Deg > 0 && Deg <= 90){ // 第一象限
let tx = halfRect + ((BigCircleRadius) * Math.sin(Angle));
let ty = halfRect - ((BigCircleRadius) * Math.cos(Angle));
return {
x: tx,
y: ty
}
}
}
五、结尾
本篇文章到这里就结束啦,各位小伙伴还想看哪些组件的原生实现,欢迎评论区里点菜。886......