工作的时候收到了 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 http://www.w3school.com.cn/tags/canvas_arc.asp
先声明一个 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 }
- }
至此, 一个进度盘已完成. 其实还有很多改进空间, 比如更合理的封装, 监听 window.resize 的时候进行大小的改变等..
实例地址: https://codepen.io/starriness/pen/bKomvX
来源: https://juejin.im/post/5b25e3396fb9a00e7a3d5161