canvas封装vue-echart第一篇 ——进度水波图

844 阅读3分钟

1.效果图

image.png

image.png

image.png image.png

2.使用插件

这里使用了math.js用于处理js的数字精度问题,使用了lodash用于深拷贝父组件数据,便于子组件不直接修改父组件传递的props

//math.js
import * as math from 'mathjs';
export default {
    // 加
    add(num1,num2){
        return math.add(math.bignumber(num1),math.bignumber(num2));
    },
    // 乘
    multiply(num1,num2){
        return math.multiply(math.bignumber(num1),math.bignumber(num2));
    },
    // 减
    subtract(num1,num2){
        return math.subtract(math.bignumber(num1),math.bignumber(num2));
    },
    // 除
    divide(num1,num2){
        return math.divide(math.bignumber(num1),math.bignumber(num2));
    }
}

2.初始化参数

  initParams() {
      this.canvas = this.$refs.canvas;
      this.oW = canvas.width;
      this.oH = canvas.height;
      this.ctx = canvas.getContext("2d");
      this.ctx.beginPath();
      this.ctx.lineWidth = this.lineWidth;
      this.r = this.oW / 2;
      this.cR = this.r - 10 * this.lineWidth;
      this.bR = this.r - 8 * this.lineWidth;
      this.axisLength = 2 * this.r - 16 * this.lineWidth;
      this.unit = this.axisLength / 9;
      this.nowrange = this.range;
      this.xoffset = 8 * this.lineWidth;
      this.soffset = -(this.PI / 2);
      let value = _.cloneDeep(this.value);
      this.decimal =
        _math.multiply(value, 100).toString().split(".")[1] /
        10 ** _math.multiply(value, 100).toString().split(".")[1].length;
      for (
      let i = this.soffset;
        i < this.soffset + 2 * this.PI;
        i += 1 / (8 * this.PI)
      ) {
        this.arcStack.push([
          this.r + this.bR * this.Cos(i),
          this.r + this.bR * this.Sin(i),
        ]);
      }
      // 圆起始点
      let cStartPoint = this.arcStack.shift();
      this.ctx.strokeStyle = "#1c86d1";
      this.ctx.moveTo(cStartPoint[0], cStartPoint[1]);
    },

3.canvas画图

将水波图进行拆分,分别是图像展示层以及功能层:其中图像展示层包含外层圆环,内层圆环,进度条,进度条背景,水波切图,中间数据展示,功能重要包含一个水波上涨动效,以及进度条动效和数据变化动效

3.1最外层圈

   drawOutCircle() {
      this.ctx.beginPath();
      this.ctx.lineWidth = this.drawCircleStyle.lineWidth;
      this.ctx.strokeStyle = this.drawCircleStyle.color;
      this.ctx.arc(this.r, this.r, this.cR + 7, 0, 2 * this.PI);
      this.ctx.stroke();
      this.ctx.restore();
    },

3.2 中间圈

    drawContentCircle() {
      this.ctx.beginPath();
      this.ctx.lineWidth = this.grayCircleStyle.lineWidth;
      this.ctx.strokeStyle = this.grayCircleStyle.color;
      this.ctx.arc(this.r, this.r, this.cR - 5, 0, 2 * this.PI);
      this.ctx.stroke();
      this.ctx.restore();
      this.ctx.beginPath();
    }

3.3 进度条圈

percentCircle() {
      this.ctx.beginPath();
      this.ctx.strokeStyle = this.CircleStyle.color;
      //使用这个使圆环两端是圆弧形状
      this.ctx.lineCap = "round";
      this.ctx.arc(
        this.r,
        this.r,
        this.cR - 5,
        0 * (Math.PI / 180.0) - Math.PI / 2,
        this.nowdata * 360 * (Math.PI / 180.0) - Math.PI / 2
      );
      this.ctx.stroke();
      this.ctx.save();
    },

3.4波浪

 drawWavesCircle() {
      this.ctx.beginPath();
      this.ctx.arc(this.r, this.r, this.cR - 10, 0, 2 * this.PI, false);
      this.ctx.clip();
    }

3.5中间显示文字百分数

    drawTextValue() {
      this.ctx.globalCompositeOperation = "source-over";
    let size = 0.4 * this.cR;
      this.ctx.font = this.textStyle.font;
      let txt =
        parseFloat((this.nowdata.toFixed(2) * 100).toFixed(0)) +
        this.decimal +
        "%";
      this.ctx.fillStyle = this.textStyle.color;
      this.ctx.textAlign = this.textStyle.textAlign;
      this.ctx.fillText(txt, this.r + 5, this.r + 20);
    },

4.功能函数

这里一共写了两个功能函数,一个用于动态渲染进度百分数。另外一个实现水波随百分数的升高上涨的函数

 //波浪上涨函数
    drawUpSine() {
      this.ctx.beginPath();
      this.ctx.save();
       //建立一个栈记录起始点和终点坐标
    let Stack = [];
      for (
      let i = this.xoffset;
        i <= this.xoffset + this.axisLength;
        i += 20 / this.axisLength
      ) {
      let x = this.sp + (this.xoffset + i) / this.unit;
      let y = this.Sin(x) * this.nowrange;
      let dx = i;
      let dy =
          2 * this.cR * (1 - this.nowdata) + (this.r - this.cR) - this.unit * y;
        this.ctx.lineTo(dx, dy);
        Stack.push([dx, dy]);
      }
      // 获取初始点和结束点
    let startP = Stack[0];
      this.ctx.lineTo(this.xoffset + this.axisLength, this.oW);
      this.ctx.lineTo(this.xoffset, this.oW);
      this.ctx.lineTo(startP[0], startP[1]);
      this.ctx.fillStyle = this.sine.color;
      this.ctx.lineWidth = 0;
      this.ctx.fill();
      this.ctx.restore();
    },
    
    
     // 调整百分值
    adjustPercent() {
      if (this.data >= 0.85) {
        if (this.nowrange > this.range / 4) {
          this.t = this.range * 0.01;
          this.nowrange -= this.t;
        }
      } else if (this.data <= 0.1) {
        if (this.nowrange < this.range * 1.5) {
          this.t = this.range * 0.01;
          this.nowrange += this.t;
        }
      } else {
        if (this.nowrange <= this.range) {
          this.t = this.range * 0.01;
          this.nowrange += this.t;
        }
        if (this.nowrange >= this.range) {
          this.t = this.range * 0.01;
          this.nowrange -= this.t;
        }
      }
      if (this.data - this.nowdata > 0) {
        this.nowdata += this.sine.waveupsp;
      }
      if (this.data - this.nowdata < 0) {
        this.nowdata -= this.sine.waveupsp;
      }

      this.data = this.value; //给固定值
      this.sp += 0.07;
    },

5.渲染函数

这里开始使用的是定时器,弄了半天没弄出来,使用了requestAnimationFrame详细用法掘金上有篇文章写的不错,地址:juejin.cn/post/699129…

   //渲染canvas
    renderCanvas() {
      this.ctx.clearRect(0, 0, this.oW, this.oH);
      //最外面色圈
      this.drawOutCircle();
      //内层色圈
      this.drawContentCircle();
      //进度圈
      this.percentCircle();
      //中间波浪
      this.drawWavesCircle();
      //调整数值
      this.adjustPercent();
      // 开始水波动画
      this.drawSine();
      // 进度文字
      this.drawTextValue();
      //动画函数
      this.targetId = requestAnimationFrame(this.render);
    },

6.初始渲染与清除动画

  mounted() {
    //初始化参数
    this.initParams();
    // 开始渲染
    this.render();
  },
  //清除动画函数
  beforeDestroy() {
    this.targetId = null;
    this.canvas = null;
    cancelAnimationFrame(this.targetId);
  },

7.prop传入

  props: {
    value: Number | Object,
    styles: {
      type: Object,
      default: {
        width: 300,
        height: 300,
      },
    },
    textStyle: {
      type: Object,
      default: {
        font: "bold 30px 黑体",
        textAlign: "center",
        color: "#000",
      },
    },
    drawCircleStyle: {
      type: Object,
      default: {
        lineWidth: 10,
        color: "purple",
      },
    },
    grayCircleStyle: {
      type: Object,
      default: {
        lineWidth: 10,
        color: "#030b30",
      },
    },
    CircleStyle: {
      type: Object,
      default: {
        color: "blue",
      },
    },
    sine: {
      type: Object,
      default: {
        color: "#43bd00",
        waveupsp: 0.006,
      },
    },

8.动态渲染

这里有个需求是根据值的变化动态的改变水波图进度,这里我使用watch监听从父组件传来的进度值

  watch: {
    //监听value值以重新渲染
    value: {
      handler(newVal, oldVal) {
        if (newVal != this.value) {
          this.initParams();
          this.render();
        }
      },
      deep: true,
      immediate: true,
    },
  },

9.使用组件

<template>
  <div>
    <CorrugatedProgressChart :value="percent" :styles="styleObj" :textStyle='textStyle' :CircleStyle="CircleStyle" :drawCircleStyle='drawCircleStyle' :grayCircleStyle='grayCircleStyle' :sine='sine'/>
  </div>
</template>

<script>
import CorrugatedProgressChart from "./components/CorrugatedProgressChart.vue";
export default {
  name: "App",
  components: {
    CorrugatedProgressChart,
  },
  data() {
    return {
      percent: 0.563,
      styleObj: {
        width: 300,
        height: 300,
      },
      textStyle: {
        font: 'bold 30px Microsoft Yahei',
        textAlign: "center",
        color: "yellow",
      },
      //最外层圈样式
      drawCircleStyle:{
        lineWidth:24,
        color:'#078782'
      },
      //中间背景圈
      grayCircleStyle:{
        lineWidth:12,
        color:'gold',
      },
      //进度圈
      CircleStyle:{
        color:'#006f6b'
      },
      sine:{
        color:"#15B4A5",
        //水上涨速度
        waveupsp:0.003
      },
      rangeVal:0,
      maxVal:100,
      minVal:0
    };
  },
  methods: {
   
  },
};
</script>