前端学习笔记:使用canvas绘制有圆角的百分比进度条

3,137 阅读6分钟

前言

依然是这个简单的项目。

由于使用JS+CSS不能实现圆角的效果(如下图,主要使用到了CSS3的transform属性),所以特地研究了一下怎么使用canvas来100%复原设计稿。

HTML中的canvas

第一眼看到canvas相关的API文档时,感觉有点头大:东西实在太多了,想要一下子学会似乎问题不小。不过看完了相关的文档之后,发现尽管API有点多,但是逻辑还是比较简单的。

HTML中的canvas通过canvas标签引入,部分浏览器似乎不支持,需要做兼容处理,这里不做过多叙述。

//html中的canvas标签
<canvas id="canvas">您的浏览器不支持canvas</canvas>

使用canvas的重点在JS部分。在JS中,第一个比较难理解的概念是如何获取canvasContext。我的第一反应是获取到了对应的HTML canvas DOM对象,就可以使用相应的API来做图。但实际上,做图相关的API不是在DOM对象上,而是在DOM对象下的canvasContext对象上。具体如下:

//这里也需要处理兼容性问题
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext('2d');
if (!ctx) {
    //兼容性处理
    console.log("您的浏览器不支持画图");
} else {
    //开始做图
}

这里我的理解是这样的:canvas这个概念模拟了现实生活中画画的概念。不过,canvas不仅可以在平面(2D空间)上作画,还可以在3D空间中作画。在JS中获取canvas DOM对象,相当于告诉浏览器,我要准备画画了。那么,我们要画的是2D的画还是3D的画呢?这就需要我们进一步告知浏览器。因此需要2个步骤。

理解完了这步以后,我们继续阅读canvas的API文档。我的目的是实现上图中的圆角效果。可以看到,上图中的圆角效果实际上是通过两个有一定宽度的圆弧组成的,其中第一个圆弧是半圆,第二个圆弧是有一定角度的扇形。canvasContext对象提供了arc()方法画圆。这个方法接收6个参数,其中第一、二个参数为圆心坐标点,第三个参数为圆的半径,第四、五个参数指定了画圆的起始角度和结束角度,起始角度是以三点钟的方向来进行计算的。第六个参数为boolen类型,指定画圆是顺时针还是逆时针。第六个参数是可选的,在不指定时,默认以顺时针方向画圆。

arc(x, y, radius, startDeg, endDeg, true);

我们不指定第六个参数,指定圆心的位置为(60, 60),指定半径为50,开始角度为Math.PI,结束角度为2*Math.PI,这样我们就画出来一个半圆啦!

ctx.arc(60, 60, 50, Math.PI, 2*Math.PI)

但是很快我们就发现另一个问题:浏览器中并没有我们画的半圆(白色部分为canvas)。这是为什么呢?

原因很简单,canvasContext画图结束时,需要指定是画出来的路径是描边(stroke方法)还是填充(fill方法)。我们两个方法都试试看看得到的结果。

ctx.stroke();
//ctx.fill();

//ctx.stroke();
ctx.fill();

这些概念都非常直观。可以看得出来,canvas实际上在最大程度模拟现实生活中的画画。想要实现上图的进度条效果,显然需要使用到的是stroke()方法。这样我们就获得了一个半圆。不过,画出来的半圆的边框颜色是黑色,看上去似乎也没有宽度,起始点和结束点也不是圆角。我们继续阅读API文档,发现这些样式在API中都有,他们分别是strokeStyle、lineWidth和lineCap,我们把这三个样式都设置一下。

ctx.strokeStyle = 'orange';
ctx.lineWidth = 10;
ctx.lineCap = 'round'

半圆进度条就画出来了!

依样画葫芦,我们再画一个20%的进度条。我们发现画出来的不是我们想要的效果。这是为什么呢?

ctx.arc(60,60,50,Math.PI,1.2*Math.PI);
ctx.strokeStyle = 'red';
ctx.lineWidth = 10;
ctx.lineCap = 'round';
//注意,这篇文章之前的代码没有以stroke()方法结尾,特地做了一个修改
ctx.stroke();

其实也很好理解,JS中后面的代码会覆盖前面的代码。画20%进度条的代码基本上和画半圆的进度条是一样的,因此前面的代码就被后面的覆盖了。对应现实中的画画,就是这里的代码实现的画画是连笔的,没有中断。上述的代码实现的效果是先画一个半圆,然后画笔再移到了第二个进度条的起始点(对应图中红色的横线),并完成20%进度条的绘画。由于最后边框的颜色指定为红色,因此在页面看不出第二个20%进度条。

那实际上,我们想要的效果是完成第一个半圆以后,把画笔从画布上移开,移动到20%进度条的起始点,再继续完成20%进度条的绘画。API中也提供了对应的方法:beginPath()。我们把这个方法放上去看看效果。

ctx.beginPath();
ctx.arc(60,60,50,Math.PI,1.2*Math.PI);
ctx.strokeStyle = 'red';
ctx.lineWidth = 10;
ctx.lineCap = 'round';
ctx.stroke();

我们看到加上了beginPath()方法以后,不但没有了前面红色的横线,而且还保留了第一个半圆进度条的样式。这样,我们就百分百复现了原型图中有圆角的进度条。按照这个思路去继续看API的其它方法和属性,是不是就一目了然了呢?

DEMO放这了

为了方便伸手党,把以上代码整合放在这里了。

<--!html部分-->
<canvas id="canvas">您的浏览器不支持canvas</canvas>

//JS部分
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
if(!ctx) {
    console.log("该浏览器不支持canvas")
} else {
    ctx.arc(60, 60, 50, Math.PI, 2 * Math.PI);
    ctx.strokeStyle = "orange";
    ctx.lineWidth = "10";
    ctx.lineCap = "round";
    ctx.stroke();
    
    ctx.beginPath();
    ctx.arc(60, 60, 50, Math.PI, 1.2 *Math.PI);
    ctx.strokeStyle = "red";
    ctx.lineWidth = 10;
    ctx.lineCap = "round";
    ctx.stroke();
}

类放这里了

为了方便大家自己设置进度条,这里再写了一个类。

ES5的类

/*
*radius:进度条半径;innerColor:内部进度条颜色;outerColor:外部进度条颜色;lineWidth:进度条宽度
*/
function Progress(radius, innerColor, outerColor, lineWidth) {
    this.radius = radius;
    this.innerColor = innerColor;
    this.outerColor = outerColor;
    this.lineWidth = lineWidth;
    this.drawProgress();
}

Progress.prototype = {
    drawProgress: function() {
        var radius = this.radius;
        var innerColor = this.innerColor;
        var outerColor = this.outerColor;
        var canvas = document.getElementById("canvas");
        var ctx = canvas.getContext("2d");
        if(!ctx) {
            console.log("该浏览器不支持canvas")
        } else {
        
            ctx.arc(radius + 10, radius + 10, radius, Math.PI, 2 * Math.PI);
            ctx.stroke();
            ctx.strokeStyle = outerColor;
            ctx.lineWidth = "10";
            ctx.lineCap = "round";
            
            ctx.beginPath();
            ctx.arc(radius + 10, radius + 10, radius, Math.PI, 1.2 *Math.PI);
            ctx.stroke();
            ctx.strokeStyle = innerColor;
            ctx.lineWidth = 10;
            ctx.lineCap = "round";
        }
    }
}

小程序中的canvas

和HTML有些不同,小程序提供了createCanvasContext()方法获取canvasContext。

另外,小程序中canvasContext的属性还支持通过对应的方法来设置。

//以下两行代码是等价的
ctx.fillStyle = "red";
ctx.setFillStyle('red');

最后,小程序除了需要以stroke()或者fill()结尾外,还要在最后加draw()方法,否则对应的页面不会显示绘制的图像或内容。