探究canvas环形进度条及其背后的原理

在银行做前端开发,最痛苦的莫过于内网开发,很多第三方库和组件无法使用,只能自己造轮子,真可谓是举步维艰!

上周写过一篇《纯css轻松实现环形进度条》,就像标题所说,实现起来确实"轻松",不过也因此受到一些限制,例如无法让进度条两头呈现圆形,也无法做出动画加载进度的效果。

那么今天就用canvas来写一个环形进度条来解决这些限制。

实现

使用canvas写一个环形进度条非常简单,只需要调用两次CanvasRenderingContext2D.arc()方法,画两段不同颜色的圆弧即可。

html 复制代码
<canvas id="canvas" width="200" height="200"></canvas>
js 复制代码
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

// 定义弧线的宽度
ctx.lineWidth = 8;
// 定义两端为圆形
ctx.lineCap = "round";

// 先画下面的底色,一个整圆
ctx.beginPath();
ctx.arc(100, 100, 50, 0, 2 * Math.PI);
// 底色用灰色
ctx.strokeStyle = "#ebeef5";
ctx.stroke();
ctx.closePath();

// 再画有颜色的部分,根据进度动态计算
ctx.beginPath();
ctx.arc(100, 100, 50, 0, 2 * Math.PI * 0.8);
ctx.strokeStyle = "#00a0fb";
ctx.stroke();
ctx.closePath();

// 在中间写上百分比
ctx.textAlign = "center";
ctx.font = "14px sans-serif";
ctx.fillText(0.8 * 100 + "%", 100, 100);

就这么简单,但你可能会说:

  • 你这跟element-ui的也不一样啊,如果我想让起点在12点钟方向呢?

看,element-ui 环形进度条的起点在正上方

  • 如果要画一个仪表盘形进度条呢?

element-ui 仪表盘形进度条

别急,下面才是重中之重!

原理

MDN中说道:CanvasRenderingContext2D.arc() 是 Canvas 2D API 绘制圆弧路径的方法。圆弧路径的圆心在 (x, y) 位置,半径为 r,根据anticlockwise (默认为顺时针)指定的方向从 startAngle 开始绘制,到 endAngle 结束。

语法:
js 复制代码
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);
参数:

x:圆弧中心(圆心)的 x 轴坐标。

y:圆弧中心(圆心)的 y 轴坐标。

radius:圆弧的半径。

startAngle:圆弧的起始点,x 轴方向开始计算,单位以弧度表示。

endAngle:圆弧的终点,单位以弧度表示。

anticlockwise(可选):可选的Boolean值,默认为 false,如果为 true,逆时针绘制圆弧,反之,顺时针绘制。

这里有两句话:

  1. x 轴方向开始计算
  2. 单位以弧度表示

这是什么意思呢?我们试着来实现上面说的两个希望实现的效果,顺便理解这两句话。

先实现仪表盘形进度条

画一个草图理解一下:

一个圆一共360度,分成四份,12点钟、3点钟、6点钟、9点钟方向分别是0°、90°、180°、270°,这个很好理解,那么很显然,仪表盘形进度条需要的就是顺时针从225°(180至270的中间)到135°(90至180的中间)这总共270°这部分

但是这里拿到的是度数,arc函数需要的是弧度,这是什么意思呢?高中时期你一定学过,忘了?没关系,一起来回顾一下:

角度定义:两条射线从圆心向圆周射出,形成一个夹角和夹角正对的一段圆弧。当这段弧长正好等于圆周长的360分之一时,两条射线的夹角的大小为1度。

假设AB这段圆弧的长度为圆周长的360分之一,那么∠AOB的度数为1°

弧度定义:两条射线从圆心向圆周射出,形成一个夹角和夹角正对的一段圆弧。当这段孤长正好等于圆的半径时,两条射线的夹角大小为1弧度。

假设AB这段圆弧的长度刚好等于圆的半径,我们就说∠AOB的大小为1弧度

另外我们都知道,圆的周长=2πr

那么如果我用周长除以半径,是不是就知道了这个圆周上有几个半径的长度,也就得到了这个圆有多少弧度

即: 2πr / r = 2π,也就是说,一个整圆的弧度为2π,即360° = 2π弧度。

所以我们知道了,如果要用canvas画出一个完整的圆,api就是:

js 复制代码
ctx.arc(x, y, r, 0弧度, 2 * Math.PI 弧度)
回到正题

画一个仪表盘形进度条,也就不是一个完整的圆,从前面的分析可知,我们要画的是顺时针方向从225°到135°这部分,那么这个角度如何转换为弧度呢?

从上面的分析知道了,360° = 2π弧度,那么180° = π弧度,那么1° = π / 180弧度。

所以就得到了角度转弧度公式:

弧度 = 角度 * π / 180

来画一段圆弧看看效果:

js 复制代码
ctx.arc(
  100, 
  100, 
  50, 
  225 * Math.PI / 180, 
  135 * Math.PI / 180
);

咦?说好的仪表盘形,怎么是这个样子的?别急,还记得前面留下的问题吗,x轴方向开始计算这句话。

也就是说,起点是在x那个位置,三点钟方向,而不在y的位置,也就是说我们计算的225~135,实际上多了90度,那就减去90度呗。

js 复制代码
ctx.arc(
  100, 
  100, 
  50, 
  (225 - 90) * Math.PI / 180, 
  (135 - 90) * Math.PI / 180
);

诶!这回就对味儿了嘛。

但现在画出来的进度值貌似是100%的,再在同一个位置画一个不同颜色,不同长度的圆弧就是了:

圆环一共270度,要画一个80%进度的,用270 * (1 - 0.8)

js 复制代码
// 先画底色
ctx.beginPath();
ctx.arc(
	100,
	100,
	50,
	(225 - 90) * (Math.PI / 180),
	(135 - 90) * (Math.PI / 180)
);
// 底色用灰色
ctx.strokeStyle = "#ebeef5";
ctx.stroke();
ctx.closePath();

// 再画体现进度的部分
ctx.beginPath();
ctx.arc(
	100,
	100,
	50,
	(225 - 90) * (Math.PI / 180),
	(135 - 270 * (1 - 0.8) - 90) * (Math.PI / 180)
);
// 进度用蓝色
ctx.strokeStyle = "#00a0fb";
ctx.stroke();

现在是不是有那味儿了?

现在我们已经知道了,canvas画圆弧的起点在x轴方向,那么要让环形进度条的起点在12点钟方向,就好办了。

起点在12点钟方向的环形进度条
js 复制代码
ctx.arc(
	100,
	100,
	50,
	((0 - 90) * Math.PI) / 180,
	((360 * 0.8 - 90) * Math.PI) / 180
);

总结

其实,如果你非常熟悉弧度的相关知识,大可不必这么复杂,无需用角度去换算弧度。一个圆的弧度是2π,半圆的弧度是π,前面多算的那90度,就是2 * Math.PI * 0.25,直接减去这个值就好了。

js 复制代码
ctx.arc(
	100,
	100,
	50,
	0 - 2 * Math.PI * 0.25,
	2 * Math.PI * 0.8 - 2 * Math.PI * 0.25
);

效果是一样的

不过我个人觉得仪表盘那个效果,还是先拿到角度再转换为弧度,比较好理解一点。

相关推荐
德育处主任18 小时前
p5.js 3D 形状 "预制工厂"——buildGeometry ()
前端·javascript·canvas
德育处主任3 天前
p5.js 3D盒子的基础用法
前端·数据可视化·canvas
掘金安东尼3 天前
2分钟创建一个“不依赖任何外部库”的粒子动画背景
前端·面试·canvas
百万蹄蹄向前冲3 天前
让AI写2D格斗游戏,坏了我成测试了
前端·canvas·trae
用户2519162427116 天前
Canvas之画图板
前端·javascript·canvas
FogLetter9 天前
玩转Canvas:从静态图像到动态动画的奇妙之旅
前端·canvas
用户25191624271110 天前
Canvas之贪吃蛇
前端·javascript·canvas
用户25191624271110 天前
Canvas之粒子烟花
前端·javascript·canvas
普兰店拉马努金10 天前
【Canvas与文字】生存与生活
生活·canvas·文字·生存
敲敲敲敲暴你脑袋13 天前
用canvas绘制兰伯特投影地图
typescript·数据可视化·canvas