工作的时候收到了UI的设计图,有一个类似进度盘(话说我也不大清楚官方叫法是啥)的效果。。就想起了荒废了好久的canvas。确认过效果,就用canvas来玩一下吧。。
UI分析
UI图如图:
那么,按层级分析,里面有几个部分:
- 空心圆(进度条的背影)
- 空心圆(进度条)
- 空心圆(实心圆的边框)
- 实心圆
- 文字(进度)
- 文字(最二行)
确认了需要绘制的部分起码有6块。
开始绘制
canvas了解
根据上面的各层级需求,大概可能会使用上的api如下
// 线宽
ctx.lineWidth
// 用于指定结束线帽的样式
ctx.lineCap
// 绘制渐变色
ctx.createLinearGradient
// 指定线条绘图颜色
ctx.strokeStyle
// 指定填充绘图颜色
ctx.fillStyle
// 创建一个圆
ctx.arc()
// 绘制文字
ctx.fillText()
// 指定文字字样
ctx.font
// 指定文字位置
ctx.textAlign
canvas画圆说明
就是ctx.arc(x, y, redius, startAngle, endAngle, counterclockwise)
这个方法可以创建一个圆形,接收6个参数,分别为 圆心x坐标,圆心y坐标, 圆半径,起始弧度,结束弧度,是否逆时针绘制
角度=180°×弧度÷π ,弧度=角度×π÷180°
一个完整圆为: 360° × π ÷ 180° = 2 × π
综上,从0开始到 Math.PI*2 为一个圆
圆的绘制路径如图所示
更详细可参考: W3school
先声明一个canvas
// HTML部分
<canvas id="canvas"></canvas>
// JS部分
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
canvas.width = Math.ceil(300 / 1920 * window.innerWidth);
canvas.height = Math.ceil(300 / 1920 * window.innerWidth);
创建一个类
class Annulus {
constructor(obj={}){
this.size = 100;
this.lineWidth = 10;
this.location = obj.location || {x: canvas.width/2, y: canvas.height/2};
}
// 绘制进度条底层
drawBg(){}
// 绘制进度条
drawCircleLay(){}
// 绘制中心的大圆
drawCenterCircle(){}
// 绘制大圆边缘的边
drawCenterBorderCircle(){}
// 绘制进度文字
drawTextPercent(){}
// 绘制进度下面的文字
drawTextName(){}
// 定义动画
animate(){
this.drawBg();
this.drawCircleLay();
this.drawCenterCircle();
this.drawCenterBorderCircle();
this.drawTextPercent();
this.drawTextName();
}
// 执行
run(){
this.animate();
}
}
进度条的背景
drawBg(){
// 绘制背景圈
ctx.beginPath();
ctx.strokeStyle = '#2d4264';
ctx.lineWidth = 10;
ctx.lineCap = "round";
ctx.arc(this.location.x, this.location.y, 100, Math.PI*0.75, Math.PI*2.25, false);
ctx.stroke();
}
进度条
drawCircleLay(){
// 绘制进度条
ctx.beginPath();
var gradient = ctx.createLinearGradient(0, this.linearLocation().start, 0, this.linearLocation().end);
gradient.addColorStop(0, '#0f6cd9');
gradient.addColorStop(1, '#05a6da');
ctx.strokeStyle = gradient;
ctx.lineWidth = this.lineWidth;
ctx.lineCap = "round";
ctx.arc(this.location.x, this.location.y, this.size, Math.PI*0.75, Math.PI*2.25, false);
ctx.stroke();
}
linearLocation(){
// 设定渐变背影的起始结束点
let start = this.location.y - ((this.size-15)*2 + this.lineWidth)/2;
let end = start + (this.size-15)*2 + this.lineWidth
return {start: start, end: end}
}
目前基本上跟背景圈一样,不同的是这里用了渐变色,先绘制出来,后再给加上变量变化
中心圆
drawCenterCircle(){
// 绘制中心圆
ctx.beginPath();
var gradient = ctx.createLinearGradient(0, this.linearLocation().start, 0, this.linearLocation().end);
gradient.addColorStop(0, '#39a8ce');
gradient.addColorStop(1, '#5647c9');
ctx.fillStyle = gradient;
ctx.arc(this.location.x, this.location.y, this.size-15, 0, Math.PI*2, false);
ctx.fill();
}
中心圆的边
drawCenterBorderCircle(){
// 绘制中心圆周边的那圈
ctx.beginPath();
ctx.strokeStyle = 'rgba(0,0,0,0.3)';
ctx.lineWidth = 10;
ctx.arc(this.location.x, this.location.y, this.size-20, 0, Math.PI*2, false);
ctx.stroke();
}
绘制进度
drawTextPercent(percent){
// 绘制进度文字
ctx.beginPath();
ctx.font = '31px Arial';
ctx.textAlign="center";
ctx.fillStyle="#192f47";
ctx.fillText(`100%`, this.location.x, this.location.y);
ctx.stroke();
}
绘制进度下面的文字
drawTextName(){
// 绘制二级文字
ctx.beginPath();
ctx.font = '14px "Microsoft YaHei"';
ctx.textAlign="center";
ctx.fillStyle="#192f47";
ctx.fillText('text', this.location.x, this.location.y+25);
ctx.stroke();
}
让它运动
接下来我们可以让进度条动起来。
运动通用的有几个点:
- 速度
speed
- 经过的路程,在这就是角度
degree
,因为是进度条,总量就是100, 这里的圈的角度是 Math.PI*2.25 - Math.PI*0.75 = Math.PI * 1.75。那么1%的角度就是 Math.PI*.1.5 / 100 - 我们还需要有一个变量来记录当前运动到的百分比
tol
综上,我们把 类的 constructor
改造一下,如下:
constructor(obj = {}){
/*
* speed -- 速度
* color -- 颜色
* size -- 大小
* lineWidth -- 线宽
* location -- 圆心位置
* text -- 文字
* value -- 圆环滚动的值 ,这里指是百分比
*/
this.speed = obj.speed || 0.1;
this.color = obj.color || '#ffeedd';
// 180 是UI的大体尺寸是在180px,以1920为基准
this.size = Math.ceil((obj.size || 80) * (canvas.width / 180));
this.lineWidth = obj.lineWidth || 10;
this.location = obj.location || {x: canvas.width/2, y: canvas.height/2};
this.textName = obj.text || '第二行文字title';
this.value = obj.value || 0;
// 这里是圆的终点减去圆的起点
this.degree = Math.PI*1.5/100;
this.animate = this.animate.bind(this);
this.tol = 0;
}
drawCircleLay()
方法的绘制结束点使用变量来控制
drawCircleLay(){
// 绘制进度条
if (this.value == 0) return;
ctx.beginPath();
var gradient = ctx.createLinearGradient(0, this.linearLocation().start, 0, this.linearLocation().end);
gradient.addColorStop(0, '#0f6cd9');
gradient.addColorStop(1, '#05a6da');
ctx.strokeStyle = gradient;
ctx.lineWidth = this.lineWidth;
ctx.lineCap = "round";
//这里改成用变量来控制
ctx.arc(this.location.x, this.location.y, this.size, Math.PI * 0.75, Math.PI*0.75+this.tol * this.degree, false);
ctx.stroke();
}
drawTextPercent()
可以把变量
tol
传进来作用当前显示的进度数
drawTextPercent(percent){
// 绘制进度文字
ctx.beginPath();
ctx.font = `${this.size / 2.5}px Arial`;
ctx.textAlign="center";
ctx.fillStyle="#192f47";
ctx.fillText(`${parseInt(percent)}%`, this.location.x, this.location.y);
ctx.stroke();
}
给
animate()
方法加上 RAF, 让其运动
animate(){
window.requestAnimationFrame(this.animate);
ctx.clearRect(0, 0, canvas.width, canvas.height);
this.drawBg();
this.drawCircleLay();
this.drawCenterCircle();
this.drawCenterBorderCircle();
this.drawTextPercent(this.tol);
this.drawTextName(this.textName);
if (this.tol < this.value) { this.tol += this.speed }
}