评论

如何在小程序中快速实现环形进度条

教你快速应对小程序常见进度条需求,快速迭代

在小程序开发过程中经常涉及到一些图表类需求,其中环形进度条比较属于比较常见的需求

[中间的文字部分需要自己实现,因为每个项目不同,本工具只实现进度条]

上图中,一方面我们我们需要实现动态计算弧度的进度条,还需要在进度条上加上渐变效果,如果每次都需要自己手写,那需要很多重复劳动,所以决定为为小程序生态圈贡献一份小小的力量,下面来介绍一下整个工具的实现思路,喜欢的给个star咯

https://github.com/lucaszhu2zgf/mp-progress

环形进度条由灰色底圈+渐变不确定圆弧+双色纽扣组成,首先先把页面结构写好:

.canvas{
    position: absolute;
    top: 0;
    left: 0;
    width: 400rpx;
    height: 400rpx;
}

因为进度条需要盖在文字上面,所以采用了绝对定位。接下来先把灰色底圈给画上:

const context = wx.createContext();
// 打底灰色曲线
context.beginPath();
context.arc(this.convert_length(200), this.convert_length(200), r, 0, 2*Math.PI);
context.setLineWidth(12);
context.setStrokeStyle('#f0f0f0');
context.stroke();


wx.drawCanvas({
    canvasId: 'progress',
    actions: context.getActions()
});

效果如下:

接下来就要画绿色的进度条,渐变暂时先不考虑

// 圆弧角度
const deg = ((remain/total).toFixed(2))*2*Math.PI;     
// 画渐变曲线
context.beginPath();
// 由于外层大小是400,所以圆弧圆心坐标是200,200
context.arc(this.convert_length(200), this.convert_length(200), r, 0, deg);
context.setLineWidth(12);
context.setStrokeStyle('#56B37F');
context.stroke();


// 辅助函数,用于转换小程序中的rpx
convert_length(length) {
    return Math.round(wx.getSystemInfoSync().windowWidth * length / 750);
}

似乎完成了一大部分,先自测看看不是满圆的情况是啥样子,比如现在剩余车位是120个

因为圆弧函数arc默认的起点在3点钟方向,而设计想要的圆弧的起点从12点钟方向开始,现在这样是没法达到预期效果。是不是可以使用css让canvas自己旋转-90deg就好了呢?于是我在上面的canvas样式中新增以下规则:

.canvas{
    transform: rotate(-90deg);
}

但是在真机上并不起作用,于是我把新增的样式放到包裹canvas的外层元素上,发现外层元素已经旋转,可是圆弧还是从3点钟方向开始的,唯一能解释这个现象的是官方说:小程序中的canvas使用的是原生组件,所以这样设置css并不能达到我们想要的效果

所以必须要在canvas画图的时候把坐标原点移动到弧形圆心,并且在画布内旋转-90deg

// 更换原点
context.translate(this.convert_length(200), this.convert_length(200));
// arc原点默认为3点钟方向,需要调整到12点
context.rotate(-90 * Math.PI / 180);
// 需要注意的是,原点变换之后圆弧arc原点也变成了0,0

真机预览效果达成预期

接下来添加环形渐变效果,但是canvas原本提供的渐变类型只有两种:

1、LinearGradient线性渐变

2、CircularGradient圆形渐变

两种渐变中离设计效果最近的是线性渐变,至于为什么能够形成似乎是随圆形弧度增加而颜色变深的效果也只是控制坐标开始和结束的坐标位置罢了

const grd = context.createLinearGradient(0, 0, 100, 90);
grd.addColorStop(0, '#56B37F');
grd.addColorStop(1, '#c0e674');


// 画渐变曲线
context.beginPath();
context.arc(0, 0, r, 0, deg);
context.setLineWidth(12);
context.setStrokeStyle(grd);
context.stroke();

来看一下真机预览效果:

非常棒,最后就剩下跟随进度条的纽扣效果了

根据三角函数,已知三角形夹角根据公式radian = 2*Math.PI/360*deg,再利用cos和sin函数可以x、y,从而计算出纽扣在各部分半圆的坐标

const mathDeg = ((remain/total).toFixed(2))*360;
// 计算弧度
let radian = '';
// 圆圈半径
const r = +this.convert_length(170);
// 三角函数cos=y/r,sin=x/r,分别得到小点的x、y坐标
let x = 0;
let y = 0;
if (mathDeg <= 90) {
  // 求弧度
  radian = 2*Math.PI/360*mathDeg;
  x = Math.round(Math.cos(radian)*r);
  y = Math.round(Math.sin(radian)*r);
} else if (mathDeg > 90 && mathDeg <= 180) {
  // 求弧度
  radian = 2*Math.PI/360*(180 - mathDeg);
  x = -Math.round(Math.cos(radian)*r);
  y = Math.round(Math.sin(radian)*r);
} else if (mathDeg > 180 && mathDeg <= 270) {
  // 求弧度
  radian = 2*Math.PI/360*(mathDeg - 180);
  x = -Math.round(Math.cos(radian)*r);
  y = -Math.round(Math.sin(radian)*r);
} else{
  // 求弧度
  radian = 2*Math.PI/360*(360 - mathDeg);
  x = Math.round(Math.cos(radian)*r);
  y = -Math.round(Math.sin(radian)*r);
}

有了纽扣的圆形坐标,最后一步就是按照设计绘制样式了

// 画纽扣
context.beginPath();
context.arc(x, y, this.convert_length(24), 0, 2 * Math.PI);
context.setFillStyle('#ffffff');
context.setShadow(0, 0, this.convert_length(10), 'rgba(86,179,127,0.5)');
context.fill();


// 画绿点
context.beginPath();
context.arc(x, y, this.convert_length(12), 0, 2 * Math.PI);
context.setFillStyle('#56B37F');
context.fill();

来看一下最终效果

最后我重新review了整个代码逻辑,并且已经将代码开源到https://github.com/lucaszhu2zgf/mp-progress,欢迎大家使用

最后一次编辑于  2020-05-27  
点赞 24
收藏
评论

29 个评论

  • brave
    brave
    2020-04-26

    [点赞再看]

    2020-04-26
    赞同 3
    回复
  • Ash_zZ
    Ash_zZ
    2021-12-29

    纽扣出不来

    2021-12-29
    赞同 2
    回复 1
    • 文科男
      文科男
      2022-01-14
      animate和needDot不能同时使用哈
      2022-01-14
      回复
  • ℳ  初 yi
    ℳ 初 yi
    2023-06-16
    const mathDeg = ((remain/total).toFixed(2))*360;remain/total这俩变量哪来的啊
    
    2023-06-16
    赞同 1
    回复
  • ERROR
    ERROR
    2021-06-25

    老哥你好,我现在在用您这个组件,但是遇到了几个小问题,想请教一下,我现在在做一个等级相关的进度条,目前设置了百分之50,半圆形的,想给半圆的末尾都加上圆角这个有方法吗?还需要在内环最末尾添加上文字,这个可以实现吗?

    2021-06-25
    赞同 1
    回复
  • Admin ²º²³
    Admin ²º²³
    2020-05-14

    dotStyle:true时不显示了~~

    2020-05-14
    赞同 1
    回复 1
    • Admin ²º²³
      Admin ²º²³
      2020-05-14
      知道了,忘记设置:
      2020-05-14
      回复
  • Q1an
    Q1an
    2020-04-30

    github下来只有一个js文件阿,有没封装成组件的代码片段阿

    2020-04-30
    赞同 1
    回复 7
    • 文科男
      文科男
      2020-04-30
      为什么要下载呢,npm安装啊
      https://www.npmjs.com/package/mp-progress
      2020-04-30
      回复
    • Q1an
      Q1an
      2020-04-30回复文科男
      npm安装了还是只有一个index.js阿,要怎样使用呢
      2020-04-30
      回复
    • 文科男
      文科男
      2020-04-30回复Q1an
      看一下文档
      2020-04-30
      1
      回复
    • Q1an
      Q1an
      2020-04-30回复文科男
      太复杂了,不会用。。我想用来做上传图片的时候显示1/9, 2/9。。。。
      2020-04-30
      回复
    • 文科男
      文科男
      2020-04-30回复Q1an
      这代码都贴出来了
      2020-04-30
      回复
    查看更多(2)
  • o0o有脾气的酸奶
    o0o有脾气的酸奶
    2020-04-26

    可以哦

    2020-04-26
    赞同 1
    回复 1
    • 文科男
      文科男
      2020-04-26
      谢谢夸奖,喜欢的话给个star哈哈
      2020-04-26
      回复
  • 
    2023-07-12

    大佬你好,我看了你的代码并改写成了uniapp写法,经过测试发现将该进度条嵌套在自定义popup组件当中,在特定时间点激活该popup,会出现进度条无法显示完全透明的情况,使用检查工具检查发现canvas元素已正常渲染但是被自定义组件覆盖了,无论如何尝试都无法让canvas始终处于最顶端....

    2023-07-12
    赞同
    回复 1
    • 文科男
      文科男
      2023-09-28
      uniapp里面哪个层级是最高的
      2023-09-28
      回复
  • 杨
    2023-06-07

    up你好,我也自己写了一个 但是我canvas会有锯齿,如图:,,请问你有碰到这样的情况吗?

    2023-06-07
    赞同
    回复
  • 鹿离
    鹿离
    2023-05-11

    请问组件方式调用,怎么在圆圈中间添加文字呢

    2023-05-11
    赞同
    回复

正在加载...

登录 后发表内容