圆角矩形进度条

1,103 阅读1分钟

最近研究了一波进度条的绘制方法,发现在有很多大神绘制纯圆形进度条或纯方形的但是又圆又方的可真是少见。所以小弟最近参考了歪果仁的方法,使用canvas来实现了一款又圆又方的进度条——没错就算圆角矩形进度条。 废话不多说先上图

效果展示

image.png image.png

流程简要说明

  • 1、先获取canvas对象
  • 2、根据宽高先绘制一个圆角矩形并计算总周长
  • 3、在通过百分比计算需要绘制的总周长
  • 4、将圆角矩形一共分成9段,圆角4段,直边5段(顶边是从中点开始,所以顶边多切了1段)
  • 5、绘制的时候,每画出一顿就用总长度减去对应边的长度,如果不能被减则取剩余边长和对应边长进行比较,取最小值进行绘制,重复流程5直至完成剩余边长的绘制
  • 6、最后就是绘制文字百分比了,其余的内容就请各位大佬参见注释了

注意

  • 1、调用drawRectProcess(this.m,'#c4c4c4','gold', 35,35,45,45,25,15, true,0.7,28,'bold','Arial'),分别传入百分比、底色、进度条颜色、x偏移、y偏移、宽、高、半径、线宽、高清晰模式、缩放比例、字体大小、字体样式、字体家族即可完成绘制
  • 2、在使用的时候注意修改canvas元素的id 和 document.getElementById("canvas"); 匹配否则会报错
  • 3、供大家学习参考,如有不足请大佬多多指正,非常感谢!如果对你有帮助可以给我点个赞嘛,小弟非常感谢!

代码


<template>
  <div>
    <canvas  id="canvas" width="120px" height="120px" ></canvas>
  </div>
</template>

<script>
export default {
  data() {
    return {
      m:10
    }
  },
  mounted() {
    this.$nextTick(() => {

      function drawRectProcess(percent,bgColor,color,coX,coY,recW,recH,r,lineWidth,windowsRatio,scaleValue,fontSize,fontStyle,fontFamily){
        /**
         * 参数列表
         * percent <int>          : 进度条百分比
         * bgColor <String>       : 进度条背景颜色
         * color <String>         : 进度条颜色
         * coX <int>              : 进度条整体X轴偏移量
         * coY <int>              : 进度条整体Y轴偏移量
         * recW <int>             : 圆角矩形宽度
         * recH <int>             : 圆角矩形高度
         * r <int>                : 圆角半径
         * lineWidth <int>        : 进度条宽度,建议内部底色比外部小 1
         * windowsRatio<boolean>  : 是否启用 devicePixelRatio ,防止抗锯齿
         * scaleValue <float>     : 图形整体缩放比例
         * fontSize <int>         : 内部百分比字体大小
         * fontStyle <String>     : 字体样式
         * fontFamily <String>    : 字体家族
         * */
        let canvas = document.getElementById("canvas");
        if(canvas===null){
          console.log('未找到canvas对象')
          return
        }
        let ctx = canvas.getContext("2d");

        if(windowsRatio){
          let width = canvas.width,height=canvas.height;
          if (window.devicePixelRatio) {
            // pc设备防止锯齿
            canvas.style.width = width + "px";
            canvas.style.height = height + "px";
            canvas.height = height * window.devicePixelRatio;
            canvas.width = width * window.devicePixelRatio;
            ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
          }
        }
        //整体缩放比例
        ctx.scale(scaleValue,scaleValue);
        if(percent>100||percent<0){
          console.log('百分比错误')
          return;
        }

        // 绘制圆角矩形进度条
        function drawPercentRect(ctx,percent,color,cOX,cOY,recW,recH,R,lineWidth) {
          /**
           *
           * 参数列表
           * ctx <object>    : canvas对象
           * percent <int>   : 进度条百分比
           * color <String>  : 进度条颜色
           * cOX <int>       : 进度条整体X轴偏移量
           * cOY <int>       : 进度条整体Y轴偏移量
           * recW <int>      : 圆角矩形宽度
           * recH <int>      : 圆角矩形高度
           * R <int>         : 圆角半径
           * lineWidth <int> : 进度条宽度,建议内部底色比外部小 1
           *
           * 使用方法 : 先绘制底色,再绘制进度条
           * */

          percent=parseInt(percent)
          //处理异常百分比
          if(percent>100){
            percent=100
          }
          if(percent<=0){
            percent=0
          }
          //字体样式
          ctx.lineWidth = lineWidth; // 线条宽度
          ctx.strokeStyle = color;// 颜色
          // 定义矩形位置长度参数
          const offsetX = cOX;// x位移
          const offsetY = cOY; // y位移
          const horizLineLength = recW; // 横边长
          const vertLineLength = recH; // 竖边长
          const cornerRadius = R; // 拐角园半径
          // 计算长度,用于百分比完成
          const cornerLength = 2 * cornerRadius * Math.PI; // 2*pi*r 计算拐角圆的周长
          const totalLength = cornerLength * 4 + horizLineLength * 2 + vertLineLength * 2; // 总的周长
          // 计算矩形的每个部分开始的累积长度
          const startT = 0;// 顶部开始起点 左边起始点
          const startTR = horizLineLength; //右上角弧长起点  右上弧长起点就在横变边长的点
          const startR = startTR + cornerLength; // 右边起点
          const startBR = startR + vertLineLength; // 右下角弧长起点
          const startB = startBR + cornerLength;// 底边起点
          const startBL = startB + horizLineLength;// 左下角弧长起点
          const startL = startBL + cornerLength;// 左边起点
          const startTL = startL + vertLineLength;// 左上角弧长起点
          const endT = startTL + cornerLength
          let accumLength,x1,x2,y1,y2,x,y,start,end
          if(percent<6){
            accumLength = percent / 100 * totalLength;// 累计长度,用来表示画到那里停止
          }
          else{
            accumLength = percent / 100 * totalLength + (horizLineLength+cornerRadius)/2;// 累计长度,用来表示画到那里停止
          }

          // 清空画布 取消就可以不清空
          //ctx.clearRect(0, 0, canvas.width, canvas.height);

          // 顶部绘制
          let d = accumLength - startT; //与起点做差计算长度
          d = Math.min(d, horizLineLength/2);
          if (d > 0) {
            // (x1,y1)是起点的坐标
            x1 = (offsetX + (cornerRadius*2+horizLineLength)/2)+lineWidth/2;// 这这里加可以画偏移
            y1 = offsetY;
            // (x2,y2)是终点的坐标 ,d是计算出来的长度
            x2 = offsetX + cornerRadius + d +horizLineLength/2;
            y2 = offsetY;
            drawLine(x1, y1, x2, y2,ctx);// 绘制直线
          }

          // 右上弧边
          d = accumLength - startTR;
          d = Math.min(d, cornerLength);
          if (d > 0) {
            x = offsetX + cornerRadius + horizLineLength;
            y = offsetY + cornerRadius;
            start = -Math.PI / 2;
            end = -Math.PI / 2 + (d / cornerLength * Math.PI / 2);
            drawCorner(x, y, start, end,ctx,cornerRadius);
          }

          // 右边
          d = accumLength - startR;
          d = Math.min(d, vertLineLength);
          if (d > 0) {
            x1 = offsetX + cornerRadius + horizLineLength + cornerRadius;
            y1 = offsetY + cornerRadius;
            x2 = offsetX + cornerRadius + horizLineLength + cornerRadius;
            y2 = offsetY + cornerRadius + d;
            drawLine(x1, y1, x2, y2,ctx);
          }

          // 右下角边
          d = accumLength - startBR;
          d = Math.min(d, cornerLength);
          if (d > 0) {
            x = offsetX + cornerRadius + horizLineLength;
            y = offsetY + cornerRadius + vertLineLength;
            start = 0;
            end = (d / cornerLength) * Math.PI / 2;
            drawCorner(x, y, start, end,ctx,cornerRadius);
          }

          // 底边
          d = accumLength - startB;
          d = Math.min(d, horizLineLength);
          if (d > 0) {
            x1 = offsetX + cornerRadius + horizLineLength;
            y1 = offsetY + cornerRadius + vertLineLength + cornerRadius;
            x2 = offsetX + cornerRadius + horizLineLength - d;
            y2 = offsetY + cornerRadius + vertLineLength + cornerRadius;
            drawLine(x1, y1, x2, y2,ctx);
          }

          // 左下弧边
          d = accumLength - startBL;
          d = Math.min(d, cornerLength);
          if (d > 0) {
            x = offsetX + cornerRadius;
            y = offsetY + cornerRadius + vertLineLength;
            start = Math.PI / 2;
            end = Math.PI / 2 + (d / cornerLength) * Math.PI / 2;
            drawCorner(x, y, start, end,ctx,cornerRadius);
          }

          // 左边
          d = accumLength - startL;
          d = Math.min(d, vertLineLength);
          if (d > 0) {
            x1 = offsetX;
            y1 = offsetY + cornerRadius + vertLineLength;
            x2 = offsetX;
            y2 = offsetY + cornerRadius + vertLineLength - d;
            drawLine(x1, y1, x2, y2,ctx);
          }

          // 左上弧边
          d = accumLength - startTL;
          d = Math.min(d, cornerLength);
          if (d > 0) {
            x = offsetX + cornerRadius;
            y = offsetY + cornerRadius;
            start = Math.PI;
            end = Math.PI + (d / cornerLength) * Math.PI / 2;
            drawCorner(x, y, start, end,ctx,cornerRadius);
          }
          // 绘制最后顶部半边
          d = accumLength - endT - 12 ;
          d = Math.min(d, horizLineLength/2) ;
          if (d > 0) {
            // (x1,y1)是起点的坐标
            x1 = offsetX + cornerRadius;
            y1 = offsetY;
            // (x2,y2)是终点的坐标 ,d是计算出来的长度
            x2 = offsetX + cornerRadius+ d ;
            y2 = offsetY;
            drawLine(x1, y1, x2, y2,ctx);// 绘制直线
          }

          // @params  (x1,y1) 起点 ,(x2,y2)终点,ctx是canvas对象
          function drawLine(x1, y1, x2, y2,ctx) {
            ctx.beginPath();
            ctx.lineCap='round'
            ctx.moveTo(x1, y1)
            ctx.lineTo(x2, y2);
            ctx.stroke();
          }
          // @params  (x,y) 起点 ,(x2,y2)终点,ctx是canvas对象,cornerRadius 圆形半径
          function drawCorner(x, y, start, end,ctx,cornerRadius) {
            ctx.beginPath();
            ctx.lineCap='round'
            ctx.arc(x, y, cornerRadius, start, end, false);
            ctx.stroke();
          }
        }

        //清除原有矩形
        ctx.clearRect(0, 0,ctx.width,ctx.height);
        //绘制底色矩形 底色百分比为100
        drawPercentRect(ctx,100,bgColor,coX,coY,recW,recH,r,lineWidth-1)
        //绘制进度条
        drawPercentRect(ctx,percent,color,coX,coY,recW,recH,r,lineWidth)
        // 绘制文字百分比
        ctx.font = `${fontStyle} ${fontSize}px ${fontFamily}`;
        if(percent===100){
          ctx.fillText(`${percent}%`, (coX+recW/2-lineWidth/2), (coY+r+lineWidth/2+recH/2));
        }else if (percent<10){
          ctx.fillText(`${percent}%`, (coX+recW/2+lineWidth/2), (coY+r+lineWidth/2+recH/2));
        }else {
          ctx.fillText(`${percent}%`, (coX+recW/2), (coY+r+lineWidth/2+recH/2));
        }

      }
      drawRectProcess(this.m,'#c4c4c4','gold',
          35,35,45,45,25,15,
          true,0.7,28,'bold','Arial')
    }, 100)
  }
};
</script>
<style  scoped>

</style>