3

要实现的需求如下:
(1)类似于油量计的图形
(2)刚打开页面时,获取数据后,油量指标(绿色区段)和中间数字,都要有动画,从0开始。
image.png

首选,当然是要给出画布啦:

<div class="main-wrapper">
  <canvas id="canvas" width="360" height="350"></canvas>
</div>

绘制函数如下:

     drawCanvas () {
      const bodyWidth = document.body.clientWidth || window.innerWidth
      // 获取初始值
      const canvas = document.getElementById('canvas')
      const ctx = canvas.getContext('2d')
      // 解决清晰度问题,让画布宽度等于屏幕宽度,高度等比缩放
      let ratio = window.devicePixelRatio || 1
      canvas.height = canvas.height / canvas.width * bodyWidth
      canvas.width = bodyWidth
      canvas.style.width = canvas.width + 'px'
      canvas.style.height = canvas.height + 'px'
      canvas.width = canvas.width * ratio
      canvas.height = canvas.height * ratio
      // 解决清晰度问题

      const cWidth = canvas.width
      const cHeight = canvas.height
      const radius = 165
      const deg0 = Math.PI / 18

      // 设置油量计的最大阈值,根据实际需要设置
      const maxStep = 10000
      // 当前的数据值
      let score = nowScore
      // 数据值根据阈值做处理
      score = score < 0 ? 0 : (score > maxStep ? maxStep : score)

      ctx.save()
      ctx.clearRect(0, 0, cWidth, cHeight)
      ctx.translate(cWidth / 2, cHeight / 2)
      ctx.rotate(13 * deg0)

      this.drawFrame(ctx, cWidth, cHeight, score, radius, deg0, maxStep, ratio)
    },
    drawFrame (ctx, cWidth, cHeight, score, radius, deg0, maxStep, ratio) {
      ctx.save()
      ctx.clearRect(-cWidth / 2, -cHeight / 2, cWidth, cHeight) // 上面旋转后中心点变了,取反

      // 底层大圆
      ctx.save()
      ctx.scale(ratio, ratio)// 解决清晰度问题
      ctx.beginPath()
      ctx.lineWidth = 12
      ctx.strokeStyle = '#E1DDF5'
      ctx.arc(0, 0, radius, 0, 28 * deg0, false)
      ctx.lineCap = 'round'
      ctx.stroke()
      ctx.restore()

      // 细分刻度线
      ctx.save()
      ctx.scale(ratio, ratio)// 解决清晰度问题
      for (var i = 0; i < 29; i++) {
        ctx.beginPath()
        ctx.lineWidth = 4
        ctx.strokeStyle = '#9C9EB9'
        ctx.moveTo(148, 0)
        ctx.lineTo(140, 0)
        ctx.stroke()
        ctx.rotate(deg0)
      }
      ctx.restore()

      // 中央文字的动画 this.scoreStep是当前帧的进度值
      ctx.save()
      ctx.scale(ratio, ratio)// 解决清晰度问题
      ctx.rotate(23 * deg0)
      ctx.textAlign = 'center'

      ctx.fillStyle = '#4F4F4F'
      ctx.font = '28px PingFangSC-Regular'
      ctx.fillText('新增', 0, -30)

      ctx.fillStyle = '#2D3142'
      ctx.font = '64px  Helvetica'
      ctx.fillText(this.scoreStep, 0, 30)

      ctx.fillStyle = '#4F4F4F'
      ctx.font = '16px  PingFangSC-Regular'
      var tip = `总计:228步`
      ctx.fillText(tip, 0, 130)

      ctx.restore()

      // 内层轨道(绿色区段, 重点)
      var angleTo = this.scoreStep / maxStep * 28 * deg0
      var xTo = radius * Math.cos(angleTo)
      var yTo = radius * Math.sin(angleTo)
      var g = ctx.createLinearGradient(0, 0, xTo, yTo)
      g.addColorStop(0, '#21D876')
      g.addColorStop(1, '#96ED44')
      ctx.save()
      ctx.scale(ratio, ratio)// 解决清晰度问题
      ctx.beginPath()
      ctx.strokeStyle = g
      ctx.lineWidth = 12
      ctx.arc(0, 0, radius, 0, angleTo, false)
      ctx.lineCap = 'round'
      ctx.stroke()
      ctx.restore()

      if (this.timerID && this.isDone) {
        // 在适当的时机要结束掉动画
        window.cancelAnimationFrame(this.timerID)
      }

      // 设置动画的速率
      const step = 500
      if (this.scoreStep + step < score) {
        this.scoreStep += step
      } else if (this.scoreStep + step >= score) {
        this.scoreStep = score
      }
      let _this = this
      if (!this.isDone) {
        // 本次渲染结束后立马进行下一帧绘制
        this.timerID = window.requestAnimationFrame(function () {
          _this.drawFrame(ctx, cWidth, cHeight, score, radius, deg0, maxStep, ratio)
        })
      }
      if (this.scoreStep === score) {
        this.isDone = true
      }
    }

然后在vue的生命周期中要加一些内容

  mounted () {
    window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame

    window.cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame || window.msCancelAnimationFrame

    this.drawCanvas()
    setTimeout(() => {
      if (this.timerID) {
        window.cancelAnimationFrame(this.timerID)
      }
    }, 10000)
  },
  beforeDestroy () {
    if (this.timerID) {
      window.cancelAnimationFrame(this.timerID)
    }
  },

穿职业装的程序媛
32 声望4 粉丝