canvas 圆形百分比进度条

由于项目需求,我要在某个地方使用圆形的百分比进度条式样(不是某任务的加载进度,而是显示某个元素占总体的百分比)。最开始借鉴的 http://codepen.io/CreativeJuiz/pen/vFBIh 中,第一排第三个的方法。用两个span分别模拟左右两个半圆,通过控制旋转这两个半圆,显示不同的百分比。但,这样有个问题。因为最终要实现的百分比是通过js计算的,那么意味着我最终是需要用js来控制旋转的角度。所以,既然要用js来控制样式,索性我想干脆就不用css3了。直接用canvas来写更好。正好之前还没实践过canvas。

在介绍具体写法前,我先做一个简单类比,介绍一下canvas画图的原理。实际生活中,我们要画画,都有这么个过程:选起始点-落笔-画-提笔-选下一个起始点-落笔-画-提笔-循环,直到完成。canvas也是一样。每次画,首先你要 moveTo(x,y) ,将canvas画笔移动到某个坐标点,然后用各种方法,如:arc() ,画图形,这一笔画完后,提笔 stroke() 注1。当然,除此之外,canvas还是有些不同的地方。比如:每次要画图形前,先应该 beginPath() 创建路径;画闭合的路径时需要在stroke() 前 closePath()。总结一下,在canvas中,整个流程应该是:beginPath() - moveTo(x,y) - arc() - stroke()。很简单吧。那么,下面就来看看具体是怎么实现的吧!

注1: 这里的 stroke() 是指渲染/绘制图形,即真正的将图形“画”出来。canvas中有两种绘制方法:stroke和fill。其实从字面意思也不难看出:stroke 是绘制线条而 fill 是填充线条所围绕的整个区域。

最开始先分析一下需求。设计师给的图是这样的:

图示中,圆形进度条由三部分构成:底部圆环(灰色的圆环背景)+彩色的进度条+百分比文字。其中彩色进度条其实就是颜色渐变。分析清楚图片后,就可以开始编写代码了。

首先写一个canvas容器

html
1
2
3
<canvas id="canvas" width="300" height="300">
<p>抱歉,您的浏览器不支持canvas</p>
</canvas>

ps:中间的p元素,只有在不支持canvas的浏览器中才会显示。考虑用户体验,此处其实应该放该canvas所画图形对应的文字描述信息。这个不难,根据实际情况替换中间内容就行了。这里我就偷个小懒😄。

js代码

然后我们就可以开始写js代码了。在js文件中,首先拿到该canvas元素,然后创建2d画布,并创建一些属性控制变量。

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
percent = 85.0, // 最终百分比
circleX = canvas.width / 2, // 中心x坐标
circleY = canvas.height / 2, // 中心y坐标
radius = 100, // 圆环半径
lineWidth = 20, // 圆形线条的宽度
fontSize = 50; // 字体大小
// 画圆
function circle(cx, cy, r) {
ctx.beginPath();
ctx.moveTo(cx + r, cy);
ctx.lineWidth = lineWidth;
ctx.strokeStyle = '#eee’;
ctx.arc(cx, cy, r, 0, Math.PI * 2);
ctx.closePath();
ctx.stroke();
}

这里用到了 closePath(),因为背景的灰色圆是闭合的线条;
lineWidth 是指线条的宽度;
strokeStyle 是指最后画线条时渲染线条的颜色;
Math.PI 是指半个圆的圆弧长,若 Math.PI/180 ,则换算成了角度,画弧线时有用到;
arc (x, y, r, start, end, anticlockwise) 其中 x,y 是圆心坐标,r 是半径,start 是起始角度,end 是结束角度,最后一个属性是布尔值-是否逆时针画(默认false)

进度条 - 弧线的方法

javasript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 画弧线
function sector(cx, cy, r, startAngle, endAngle, anti) {
ctx.beginPath();
ctx.moveTo(cx, cy + r); // 从圆形底部开始画
ctx.lineWidth = lineWidth;
// 渐变色 - 可自定义
var linGrad = ctx.createLinearGradient(
circleX, circleY - radius - lineWidth, circleX, circleY + radius + lineWidth
);
linGrad.addColorStop(0.0, '#ec847a');
linGrad.addColorStop(0.5, '#9bc4eb');
linGrad.addColorStop(1.0, '#eccd23');
ctx.strokeStyle = linGrad;
// 圆弧两端的样式
ctx.lineCap = 'round';
// 圆弧
ctx.arc(
cx, cy, r,
startAngle * (Math.PI / 180.0) + (Math.PI / 2),
endAngle * (Math.PI / 180.0) + (Math.PI / 2),
anti
);
ctx.stroke();
}

这里没有用到 closePath(),因为不需要闭合。有兴趣的朋友可以在 ctx.stroke() 前加上 ctx.closePath() 试试,看是什么效果;
createLinearGradient(x0,y0,x1,y1) 是创建线性渐变,其中 x0,y0 是指线性渐变起始坐标,x1,y1 是终止坐标;
addColorStop 是添加渐变的点

动画效果

最后,考虑用户体验,加载百分比时,我们可以做一个动画效果,而且,在加载快结束时,减慢速度。

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 刷新
function loading() {
if (process >= percent) {
clearInterval(circleLoading);
}
// 清除canvas内容
ctx.clearRect(0, 0, circleX * 2, circleY * 2);
// 中间的字
ctx.font = fontSize + 'px April';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = '#999';
ctx.fillText(parseFloat(process).toFixed(0) + '%', circleX, circleY);
// 圆形
circle(circleX, circleY, radius);
// 圆弧
sector(circleX, circleY, radius, 0, process / 100 * 360);
// 控制结束时动画的速度
if (process / percent > 0.90) {
process += 0.30;
} else if (process / percent > 0.80) {
process += 0.55;
} else if (process / percent > 0.70) {
process += 0.75;
} else {
process += 1.0;
}
}
var process = 0.0; // 进度
var circleLoading = window.setInterval(function () {
loading();
}, 20);

clearReck(x0,y0,x1,y1) 清除矩形(x0,y0 矩形左上角起始坐标,x1,y1 矩形右下角结束坐标)中的所有内容,每次刷新的时候需要清除之前绘制的效果;

loading期间,文字和进度是通过变量动态控制的。那么至此,整个效果就做完啦!

下面是demo

See the Pen 圆形进度条 by geekwen (@geekwen) on CodePen.