画了一个canvas环形图,拿去用吧~(原生和小程序2个版本)

最近作的这个小程序,因为UI设计图已经经业务方确认过,这个环形图没有商量的余地(好比用插件什么的),抱着正好玩一玩canvas的心态,自个看了点资料,画了一个。(包括原生js版和小程序版)javascript

效果图:

需求:

  • 每个月任务进度的百分比会由后台返回,是一个动态传递的数值
  • 百分比不一样,彩色圆环的长度不一样
  • 彩色圆环要渐变色
  • 彩色圆环尽头有个icon,也要跟着走动

原生js版本

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <canvas id="circle" width="500" height="500"></canvas>
    <script type="text/javascript">
        circle('0.68');
        function circle(percent) {
            var canvas = document.getElementById('circle');
            var imgObj = new Image();
            imgObj.src = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1572866759391&di=af21821454e4acb13e6ba85150ab690d&imgtype=0&src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F01708b55ed1f8e6ac7251df808d695.jpg"; // 弧形结束点的图片,若是失效了,本身随便替换一个网络图片
            var ctx;
            //待图片加载完后,将其显示在canvas上,避免其没法显示
            imgObj.onload = function(){ //onload必须使用
            ctx = canvas.getContext("2d");
            /*填充文字*/
            ctx.font = "24px Microsoft YaHei";
            /*文字颜色*/
            ctx.fillStyle = '#999';
            /*文字内容*/
            var insertContent = '本月任务进度';
            var text = ctx.measureText(insertContent);
            /*插入文字,后面两个参数为文字在画布中的坐标点*/
            /*此处注意:text.width得到文字的宽度,而后就能计算出文字居中须要的x值*/
            ctx.fillText(insertContent, (360 - text.width) / 2, 240);

            /*填充百分比*/
            ctx.font = "80px Microsoft YaHei";
            ctx.fillStyle = '#222';
            const percentArr = percent.split('.');
            var ratioStr = percentArr[1] + '%';
            var text = ctx.measureText(ratioStr);
            ctx.fillText(ratioStr, (360 - text.width) / 2, 180);

            /*开始圆环*/
            var circleObj = {
                ctx: ctx,
                /*圆心坐标*/
                x: 180,
                y: 180,
                /*半径,下方出现的150都是半径*/
                radius: 150,
                /*环的宽度*/
                lineWidth: 24
            }

            /*灰色的圆环*/
            /*开始的度数-从上一个结束的位置开始*/
            circleObj.startAngle = 0; // 注意这里的0是3点钟方向,而非12点方向,和数学里的不同
            /*结束的度数*/
            circleObj.endAngle = Math.PI * 2;
            circleObj.color = '#E7EFF4';
            drawCircle(circleObj);

            /*有色的圆环*/
            /*从-90度的地方开始画*/
            circleObj.startAngle = 1.5*Math.PI; //把起始点改为数学里的12点方向
            /*从当前度数减去-90度*/
            let holeCicle = 2 * Math.PI;
            let jiaodu = percent * 360; //圆弧的角度

            let x1 = 180 + (150 * Math.sin(jiaodu * Math.PI/180)) - 25; // 为啥要减去25,由于icon图标的宽度为50,而定位是以icon左上角为基准的
            let y1 = 180 - 150 +  (150 - 150 * Math.cos(jiaodu * Math.PI/180)) - 25;
            circleObj.endAngle = 1.5*Math.PI + percent*holeCicle;
            var gnt1 = ctx.createLinearGradient(0,0,x1,y1);
            gnt1.addColorStop(0,'#FF8941');
            gnt1.addColorStop(0.3,'#FF8935');
            gnt1.addColorStop(1,'#FFC255');
            // ctx.strokeStyle = gnt1;
            circleObj.color = gnt1;
            drawCircle(circleObj);
            // 这里的this指的是imgObj,第二三个参数是它的坐标,四五个是长款
            ctx.drawImage(this, x1, y1, 50, 50);
            }

        }
        /*画曲线*/
        function drawCircle(circleObj) {
            var ctx = circleObj.ctx;
            ctx.beginPath();
            ctx.arc(circleObj.x, circleObj.y, circleObj.radius, circleObj.startAngle, circleObj.endAngle, false);
            //设定曲线粗细度
            ctx.lineWidth = circleObj.lineWidth;
            //给曲线着色
            ctx.strokeStyle = circleObj.color;

            //链接处样式
            ctx.lineCap = 'round';
            //给环着色
            ctx.stroke();
            ctx.closePath();
        }
    </script>
</body>
</html>
复制代码

一开始我是用原生写的,他大爷的,转移到小程序发现,怎么api都长的不同!还有不少隐藏的深坑,好比拿不到图片对象啊,显示不出来啊,还好,最后顺利搞定。html

小程序版

js:

Page({
  data: {
    canvas: {
      height: 0,
      width: 0
    },
    canvasWidth: 100, // 这里拜托必定要给个初始宽度啊~否则iPhone6以及华为手机会偶尔没法显示,并且是锁屏后打开又莫名其妙显示的那种……
    x:0,
    y:0, // 圆心坐标
    r:0, // 半径
  },

  onReady() {
    let rpx = this.getRatio();
    let canvasWidth = rpx * 300;
    let r = rpx * 75;
    let x = canvasWidth / 2;
    let y = canvasWidth / 2;
    this.setData({
      canvasWidth,
      r,
      x,
      y,
      rpx
    });
    this.drawRing(68%);
  },

  // 获取画布宽度相对于375的比例,方便适配各类屏幕
  getRatio() {
    let w = 0;
    wx.getSystemInfo({
      success: function (res) {
        w = res.windowWidth / 375;//按照750的屏宽
      },
    })
    return w
  },

  drawRing(percent) {
    const {r, x, y, rpx, canvasWidth} = this.data;
    let url = "success.png";
    var ctx;
    var ctx2; // 这里也是很迷,原生js只用建立一个,这里却要2个,分别用来盛放灰色圈和彩色圈
    //待图片加载完后,将其显示在canvas上
    ctx = wx.createCanvasContext('circle');
    ctx2 = wx.createCanvasContext('circle2');
    /*填充文字*/
    ctx.setFontSize(12 * rpx);
    ctx2.setFontSize(40 * rpx);
    /*文字颜色*/
    ctx.setFillStyle('#999');
    /*文字内容*/
    var insertContent = '本月任务进度';
    var text = ctx.measureText(insertContent);
    /*插入文字,后面两个参数为文字的位置*/
    /*此处注意:text.width得到文字的宽度,而后就能计算出文字居中须要的x值*/
    ctx.fillText(insertContent, (canvasWidth - text.width)/2, 185 * rpx);

    /*填充百分比*/
    ctx2.setFillStyle('#222');
    var ratioStr = this.data.options.clockRate;
    var text2 = ctx2.measureText(ratioStr);
    ctx2.fillText(ratioStr, (canvasWidth - text2.width) / 2, 165*rpx);

    /*开始圆环*/
    var circleObj = {
        ctx: ctx,
        /*圆心*/
        x: x,
        y: y,
        /*半径*/
        radius: r,
        /*环的宽度*/
        lineWidth: 12 * rpx,
        startAngle: 0,
        endAngle: 0,
        color: ''
    }

    /*灰色的圆环*/
    /*开始的度数-从上一个结束的位置开始*/
    circleObj.startAngle = 0;
    /*结束的度数*/
    circleObj.endAngle = Math.PI * 2;
    circleObj.color = '#E7EFF4';
    this.drawCircle(circleObj);

    /*有色的圆环*/
    var coloredObject = {
      ctx: ctx2,
      /*圆心*/
      x: x,
      y: y,
      /*半径*/
      radius: r,
      /*环的宽度*/
      lineWidth: 12 * rpx,
      startAngle: 0,
      endAngle: 0,
      color: ''
  }
    /*从-90度的地方开始画*/
    coloredObject.startAngle = 1.5*Math.PI;
    /*从当前度数减去-90度*/
    let holeCicle = 2 * Math.PI;

    const percentArr = percent.split('.');
    var ratioStr = percentArr[1] + '%';
    const rate = percent
    let x1 = x + (r * Math.sin(Number(rate) * 360 * Math.PI/180)) - 30*rpx/2;
    let y1 = y - r +  (r - r * Math.cos((Number(rate) * 360) * Math.PI/180)) - 30*rpx/2;
    coloredObject.endAngle = 1.5*Math.PI + Number(rate)*holeCicle;
    var gnt1 = ctx2.createLinearGradient(0,0,x1,y1);
    gnt1.addColorStop(0,'#FF8941');
    gnt1.addColorStop(0.3,'#FF8941');
    gnt1.addColorStop(1,'#FFC255');
    coloredObject.color = gnt1;
    this.drawCircle(coloredObject, x1, y1);
    // }

},

  /*画曲线*/
  drawCircle(circleObj, x1, y1) {
    var ctx = circleObj.ctx;
    ctx.beginPath();
    ctx.arc(circleObj.x, circleObj.y, circleObj.radius, circleObj.startAngle, circleObj.endAngle, false);
    //设定曲线粗细度
    ctx.setLineWidth(circleObj.lineWidth);
    //给曲线着色
    ctx.setStrokeStyle(circleObj.color);

    //链接处样式
    ctx.setLineCap('round');
    //给环着色
    ctx.stroke();
    ctx.closePath();
    if (x1 && y1) {
      ctx.drawImage('success.png', x1, y1, 30*this.data.rpx, 30*this.data.rpx);
    }
    ctx.draw();
  },
});
复制代码

wxml:

这里也有一个坑,那就是原生js直接经过id属性获取canvas画布对象就好,可是,小程序里必定要搞个canvas-id属性……java

<canvas wx:if="{{canvasWidth}}" canvas-id="circle" id="circle" style="height: {{canvasWidth || 100}}px;width: {{canvasWidth || 100}}px;margin:0 auto;"></canvas>
<canvas wx:if="{{canvasWidth}}" canvas-id="circle2" id="circle2" style="height: {{canvasWidth}}px;width: {{canvasWidth}}px;position:absolute;top:0;left:50%;margin-left:-{{canvasWidth/2}}px;"></canvas>
复制代码

好了,到此就大功告成咯~这玩意就是有点繁琐,而后须要一丢丢三角函数(只要你想找,公式处处都有啦)canvas

由于写了有大半个月了,也不记得原生js版借鉴了哪些文章,抱歉不能列出……小程序

相关文章
相关标签/搜索