【如何实现一个简单的canvas动态水球图】

**

如何实现一个简单的canvas动态水球图。

**
由于在项目中遇到有个制作一个水球图需求,在网上查找相关资料比较少,样式又不符合预期,在这样的情况下封装了一个自己可更改、定制化的水球图动效组件。

效果图:
水球图动效

代码如下:
1.封装组件:

<template>
  <div class="sun-waterpolo_conrainer" :style="'box-shadow: 0 0 20px '+lineColor+';width: '+width+'px; height: '+width+'px;'">
      <span v-show="text">{
   
   {text}}</span>
      <span v-show="!text">{
   
   {num|rounding}}<span class="unit"> {
   
   {unit}}</span></span>
    <div class="sun-canvas_conrainer" :style="'box-shadow: 0 0 5px '+lineColor+' inset;width: '+width+'px; height: '+width+'px;'">
      <canvas :id="canvasId"></canvas>
    </div>
  </div>
</template>

<script>
export default {
      
      
  data () {
      
      
    return {
      
      
      oCanvas: null,
      oContext: null,
      options: null
    }
  },

  filters: {
      
      
    rounding (value) {
      
      
      if (value === 0) {
      
      
        return '0.00'
      }
      if (!value || isNaN(value)) {
      
      
        return '--'
      }
      value = Number(value)
      if (typeof obj === 'number' && value % 1 === 0) {
      
      
        value = value + '.00'
      } else {
      
      
        value = value.toFixed(2)
      }
      return value
    }
  },
  props: {
      
      
    text: {
      
      
      type: [String, Number],
      default: ''
    },
    num: {
      
      
      type: [String, Number],
      default: '40'
    },
    unit: {
      
      
      type: String,
      default: '%'
    },
    canvasId: {
      
      
      type: String,
      default: 'canvasId_1'
    },
    color: {
      
      
      type: String,
      default: '#01c6df'
    },
    lineColor: {
      
      
      type: String,
      default: '#01c6df'
    },
    width: {
      
      
      type: Number,
      default: 100
    }
  },
  mounted () {
      
      
    this.init()
  },
  methods: {
      
      
    init () {
      
      
      this.oCanvas = document.getElementById(this.canvasId)
      this.context = this.oCanvas.getContext('2d')
      this.oCanvas.width = this.width
      this.oCanvas.height = this.width
      this.options = {
      
      
        value: 40,
        a: this.width / 8, // 振幅
        pos: [1, this.width * 0.5], // 水球图位置
        r: this.width / 2 - 1, // 水球图半径
        color: [this.color, this.color, this.color, this.color]
        // color: ['#2E5199', '#1567c8', '#1593E7', '#42B8F9']
      }
      this.start(this.options)
    },
    /**
     * 绘制图表
     */
    start (options) {
      
      
      this.context.translate(options.pos[0], options.pos[1])
      this.context.font = 'normal 16px Arial'
      this.context.textAlign = 'center'
      this.context.textBaseLine = 'baseline'
      this.createParams(options)
      requestAnimationFrame(this.startAnim) // 循环动画
    },

    // 生成水波动画参数
    createParams (options) {
      
      
      options.w = [] // 存储水波的角速度
      options.theta = [] // 存储每条水波的位移
      for (let i = 0; i < 4; i++) {
      
      
        options.w.push(Math.PI / (100 + 20 * Math.random()))
        options.theta.push(20 * Math.random())
      }
    },

    // 绘制水波线
    drawWaterLines (options) {
      
      
      let offset
      let A = options.a // 正弦曲线振幅
      let y, x, w, theta
      let r = options.r
      // 遍历每一条水纹理
      for (let line = 0; line < 4; line++) {
      
      
        this.context.save()
        // 每次绘制时水波的偏移距离
        theta = Math.random()
        offset =
          r +
          A / 2 -
          ((r * 19) / 8 + A) * (options.value / 100) +
          (line * r) / 12
        // 获取正弦曲线计算参数
        w = options.w[line]
        theta = options.theta[line]
        this.context.fillStyle = options.color[line]
        this.context.moveTo(0, 0)
        this.context.beginPath()
        for (x = 0; x <= 2 * r; x += 0.1) {
      
      
          y = A * Math.sin(w * x + theta) + offset
          // 绘制点
          this.context.lineTo(x, y)
        }
        // 绘制为封闭图形
        this.context.lineTo(x, r)
        this.context.lineTo(x - 2 * r, r)
        this.context.lineTo(0, A * Math.sin(theta) - options.width)
        this.context.closePath()
        // 填充封闭图形
        this.context.fill()
        // 截取水波范围,绘制文字
        this.context.clip()
        // this.context.fillStyle = '#071C5C'
        // this.context.fillText(parseInt(options.value, 10) + '%', 1, 1)
        this.context.restore()
      }
    },

    // 绘制最底层的深色文字
    drawText1 (options) {
      
      
      this.context.fillStyle = options.color[0]
      this.context.fillText(parseInt(options.value, 10) + '%', 1, 1)
    },

    // 帧动画循环
    startAnim () {
      
      
      this.options.theta = this.options.theta.map(item => item - 0.03)
      this.options.value += this.options.value > 100 ? 0 : 0.1
      this.options.value = this.options.value > 40 ? 40 : this.options.value
      this.context.save()
      this.resetClip(this.options) // 剪切绘图区
      // this.drawText1(this.options)// 绘制蓝色文字
      this.drawWaterLines(this.options) // 绘制水波线
      this.context.restore()
      requestAnimationFrame(this.startAnim)
    },

    // 重新剪裁绘图区域
    resetClip (options) {
      
      
      let r = options.r
      this.context.strokeStyle = this.lineColor
      this.context.fillStyle = '#071C5C'
      this.context.lineWidth = 1
      this.context.beginPath()
      this.context.arc(r, 0, r, 0, 2 * Math.PI, false)
      this.context.closePath()
      this.context.fill()
      this.context.shadowColor = this.lineColor
      this.context.shadowBlur = 1
      this.context.shadowOffsetX = 0
      this.context.shadowOffsetY = 0
      this.context.stroke()
      this.context.beginPath()
      this.context.arc(r, 0, r + 1, 0, 2 * Math.PI, true)
      this.context.clip()
    }
  }
}
</script>
<style scoped>
.sun-waterpolo_conrainer{
      
      
  border-radius: 50%;
  position: relative;
}
.sun-canvas_conrainer{
      
      
  display: inline-block;
  overflow: hidden;
  border-radius: 50%;
  position: relative;
}
.sun-waterpolo_conrainer>span{
      
      
  position: absolute;
  top: 50%;
  margin-top: -9px;
  display: inline-block;
  width: 200%;
  left: 50%;
  margin-left: -100%;
  font-size: 18px;
  color: #ffffff;
  text-shadow: 0 3px 3px rgba(0,0,0,1);
  text-align: center;
  z-index: 9;
}
.sun-waterpolo_conrainer>span .unit{
      
      
  font-size: 12px;
}
</style>

2.如何引用:

<template>
	<WaterPolo :width="90" :height="90" lineColor="#01c6df" text="40%" canvasId="liquidFill_1" color="rgba(13,245,249,.5)"></WaterPolo>
	<WaterPolo :width="90" :height="90" lineColor="#c2b128" text="40%" canvasId="liquidFill_2" color="rgba(243,214,22,.5)"></WaterPolo>
</template>
<script>
	import WaterPolo from '@/components/waterpolo/waterpolo'
	export default {
      
      
	  components: {
      
      
	    WaterPolo
	  }
	}
</script>

这样我们就能实现自己喜欢的水球图动画了,冲冲冲!!!。

猜你喜欢

转载自blog.csdn.net/weixin_42927679/article/details/125183535