嫌圖標插件太大?canvas自制羅盤儀

最近項目須要作一個羅盤儀效果,網上找了下圖表插件,感受都挺大;改爲本身須要的ui又十分麻煩,乾脆本身寫個練練手,說幹就幹。javascript

簡單的羅盤儀效果css

效果圖以下 html

img
貼代碼(基於VUE)

<template>
  <div class="gauge">
    <div class="progress" ref="progress">
      <canvas>瀏覽器不支持Canvas,請升級或改用其它瀏覽器</canvas>
    </div>
    <div class="invite">{{text}}</div>
  </div>
</template>
複製代碼

邏輯仍是比較簡單的java

先初始化一個canvas,定義canvas的寬高canvas

  • 須要注意的是canvas元素有本身默認的寬高: 默認寬 300px, 高 150px
  • canvas.width,canvas.height 和 canvas.style.width,canvas.style.height是兩個不同的概念,canva.width|height 是canvas畫布真實的寬高,而canva.style.width|height 是canvas畫布容器的寬高;真是寬高在容器寬高內會進行縮放; 如圖
    img
  • 在Retina屏上面,canvas會出現模糊的狀況;這是由於 canvas 不是矢量圖,而是像圖片同樣是位圖模式的。高 dpi 顯示設備意味着每平方英寸有更多的像素。也就是說二倍屏,瀏覽器就會以2個像素點的寬度來渲染一個像素,該 canvas 在 Retina 屏幕下至關於佔據了2倍的空間,至關於圖片被放大了一倍,所以繪製出來的圖片文字等會變模糊。因此咱們須要算出屏幕像素比 ratio = window.devicePixelRatio || 1

如今,咱們要開始畫畫了;首先是畫一個半圓,而後是進度條(小球的尾巴),還有小球;這裏比較麻煩的是小球和進度條的運行軌跡,由於小球和進度條的運行軌跡都是基於半圓的圓弧瀏覽器

  • 小球徹底是在圓弧上面運動的,小球的半徑爲圓弧的寬度 / 2;重點是要算出當前小球的圓心座標才能畫出小球的位置和形狀;因此咱們須要定義一個單位時間的弧度值angle,來計算小球單位時間的位置,speed不斷增長,小球不斷變化位置,每次變化位置,須要抹去以前畫的小球,進度條;接着就是套公式了
    img
  • 進度條,這個簡單,只半圓弧的基礎上,另外畫一條弧,弧度爲總弧度(Math.PI)和當前進度的比值

如今開始,要讓羅盤儀動起來!!定義一個變量speed(表示增長的弧度值),經過 requestAnimationFrame 進行動畫,更顯平滑流暢,空值speed的速度,就能夠空值羅盤儀的變化方式動畫

最後,給羅盤儀加點文字;主要用到fillStyle,fillText,textAlign;這裏有個坑須要注意一下,textAlign的是相對於畫布中fillText的起始座標來的;跟css的textAlign不同 ui

img
上圖起始點的座標都在中間,textAlign展示形式不一樣。

全部的邏輯代碼,整合以下:this

const ratio = window.devicePixelRatio || 1  // Retina上面顯示模糊,兼容蘋果手機

const bound = {
  start: Math.PI + 0.1,
  end: Math.PI * 2 - 0.1
}

const colors = [{ // 定義顏色,不一樣等級,弧度的顏色不同
  start: 'ffb488',
  end: 'ffddc2'
}, {
  start: 'ffb488',
  end: 'ffddc2'
}, {
  start: 'babfcd',
  end: 'dde1eb'
}, {
  start: 'e4b23f',
  end: 'ffe892'
}]

let ctx = null
let r = 0 // 半徑
let lineWidth = 0
let layerColor = 'rgba(255,255,255, 0.5)'
let width = 0
let height = 0
let angle = 0.1
let endAngle = 0
let speed = 0.04
let lineCap = 'round'
let color = null

export default {
  data () {
    return {
      canvas: null,
      width: 0,
      height: 0,
      color: {}
    }
  },
  props: {
    rate: {
      type: Number || String,
      default: 0
    },
    count: {
      type: Number || String,
      default: 0
    },
    silver: {
      type: Number || String,
      default: 0
    },
    level: {
      type: Number,
      default: 1
    },
    text: {
      type: String,
      default: ''
    }
  },
  methods: {
    initCanvas () {
      const container = this.$refs['progress']
      const width = ~~container.clientWidth
      const height = ~~container.clientHeight
      this.canvas = container.getElementsByTagName('canvas')[0]
      this.canvas.width = width * ratio
      this.canvas.height = height * ratio
      this.canvas.style.width = width + 'px'
      this.canvas.style.height = height + 'px'
      this.color = colors[ this.level - 1 ]
      // this.canvas.getContext('2d').scale(ratio, ratio)
    },
    layer () {  // 半圓
      const grd = ctx.createLinearGradient(0, height, width, height)
      grd.addColorStop(0, layerColor)
      grd.addColorStop(1, layerColor)
      ctx.beginPath()
      ctx.strokeStyle = grd
      ctx.lineWidth = lineWidth
      ctx.lineCap = lineCap
      ctx.arc(width / 2, height, r, bound.start, bound.end)
      ctx.stroke()
      ctx.closePath()
    },
    ball () { // 小圓球
      const start = Math.max(angle, 0)
      const end = Math.min(angle, Math.PI - 0.1)
      ctx.beginPath()
      ctx.fillStyle = '#fff'
      ctx.arc(width / 2 - Math.cos(start) * r, height - Math.sin(end) * r, lineWidth / 2 + 2, 0, Math.PI * 2)
      ctx.fill()
      ctx.closePath()
    },
    step () { // 進度條
      const start = Math.min(Math.PI + angle, bound.start)
      const end = Math.min(Math.PI + angle, bound.end)
      const progressGrd = ctx.createLinearGradient(0, height, width, height)
      progressGrd.addColorStop(0, `#${color.start}`)
      progressGrd.addColorStop(1, `#${color.end}`)
      ctx.beginPath()
      ctx.strokeStyle = progressGrd
      ctx.lineWidth = lineWidth
      ctx.lineCap = lineCap
      ctx.arc(width / 2, height, r, start, end)
      ctx.stroke()
      ctx.closePath()
    },
    animate () {
      if (endAngle < angle) {
        return window.cancelAnimationFrame(this.animate)
      } else {
        ctx.clearRect(0, 0, width, height)
        this.setText()
        this.layer()
        this.step()
        this.ball()
        window.requestAnimationFrame(this.animate)
        angle += speed // 勻速增長
      }
    },
    setText () {
      ctx.font = `${12 * ratio}px 微軟雅黑`
      ctx.fillStyle = '#fff'
      ctx.textAlign = 'center'
      ctx.fillText('已邀會員', width / 2, height / 2, width)
      ctx.font = `${32 * ratio}px 微軟雅黑`
      ctx.fillText(this.count || 0, width / 2, height - 5, width)
    },
    init () {
      this.initCanvas()
      ctx = this.canvas.getContext('2d')
      width = this.canvas.width
      height = this.canvas.height
      color = this.color
      lineWidth = ~~(width / 18)
      r = width / 2 - lineWidth
      endAngle = Math.max(Math.PI * this.rate, angle)
      this.animate()
    }
  },
  watch: {
    rate: {
      handler () {
        this.init()
      }
    }
  },
  beforeDestroy () {
    angle = 0.1
    endAngle = 0
  }
}
複製代碼

差很少就這麼多,下班spa

相關文章
相關標籤/搜索