index.vue 3.73 KB
Newer Older
Nature's avatar
Nature committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
<template>
  <canvas ref="bubble" :width="width" :height="height" :style="style"/>
</template>

<script>
export default {
  props: {
    y: {
      type: Number,
      default: 0,
    },
  },
  data () {
    return {
      width: 50,
      height: 80,
    }
  },
  computed: {
    distance () {
      return Math.max(0, Math.min(this.y * this.ratio, this.maxDistance))
    },
    style () {
      return `width:${this.width / this.ratio}px;height:${this.height / this.ratio}px`
    },
  },
  watch: {
    y () {
      this._draw()
    },
  },
  created () {
    this.ratio = 1
    this.width *= this.ratio
    this.height *= this.ratio
    this.initRadius = 18 * this.ratio
    this.minHeadRadius = 12 * this.ratio
    this.minTailRadius = 5 * this.ratio
    this.initArrowRadius = 10 * this.ratio
    this.minArrowRadius = 6 * this.ratio
    this.arrowWidth = 3 * this.ratio
    this.maxDistance = 40 * this.ratio
    this.initCenterX = 25 * this.ratio
    this.initCenterY = 25 * this.ratio
    this.headCenter = {
      x: this.initCenterX,
      y: this.initCenterY,
    }
  },
  mounted () {
    this._draw()
  },
  methods: {
    _draw () {
      const bubble = this.$refs.bubble
      let ctx = bubble.getContext('2d')
      ctx.clearRect(0, 0, bubble.width, bubble.height)

      this._drawBubble(ctx)

      this._drawArrow(ctx)
    },
    _drawBubble (ctx) {
      ctx.save()
      ctx.beginPath()

      const rate = this.distance / this.maxDistance
      const headRadius = this.initRadius - (this.initRadius - this.minHeadRadius) * rate

      this.headCenter.y = this.initCenterY - (this.initRadius - this.minHeadRadius) * rate

      // 画上半弧线
      ctx.arc(this.headCenter.x, this.headCenter.y, headRadius, 0, Math.PI, true)

      // 画左侧贝塞尔
      const tailRadius = this.initRadius - (this.initRadius - this.minTailRadius) * rate
      const tailCenter = {
        x: this.headCenter.x,
        y: this.headCenter.y + this.distance,
      }

      const tailPointL = {
        x: tailCenter.x - tailRadius,
        y: tailCenter.y,
      }
      const controlPointL = {
        x: tailPointL.x,
        y: tailPointL.y - this.distance / 2,
      }

      ctx.quadraticCurveTo(controlPointL.x, controlPointL.y, tailPointL.x, tailPointL.y)

      // 画下半弧线
      ctx.arc(tailCenter.x, tailCenter.y, tailRadius, Math.PI, 0, true)

      // 画右侧贝塞尔
      const headPointR = {
        x: this.headCenter.x + headRadius,
        y: this.headCenter.y,
      }
      const controlPointR = {
        x: tailCenter.x + tailRadius,
        y: headPointR.y + this.distance / 2,
      }
      ctx.quadraticCurveTo(controlPointR.x, controlPointR.y, headPointR.x, headPointR.y)

      ctx.fillStyle = 'rgb(170,170,170)'
      ctx.fill()
      ctx.strokeStyle = 'rgb(153,153,153)'
      ctx.stroke()
      ctx.restore()
    },
    _drawArrow (ctx) {
      ctx.save()
      ctx.beginPath()

      const rate = this.distance / this.maxDistance
      const arrowRadius = this.initArrowRadius - (this.initArrowRadius - this.minArrowRadius) * rate

      // 画内圆
      ctx.arc(this.headCenter.x, this.headCenter.y, arrowRadius - (this.arrowWidth - rate), -Math.PI / 2, 0, true)

      // 画外圆
      ctx.arc(this.headCenter.x, this.headCenter.y, arrowRadius, 0, Math.PI * 3 / 2, false)

      ctx.lineTo(this.headCenter.x, this.headCenter.y - arrowRadius - this.arrowWidth / 2 + rate)
      ctx.lineTo(this.headCenter.x + this.arrowWidth * 2 - rate * 2, this.headCenter.y - arrowRadius + this.arrowWidth / 2)

      ctx.lineTo(this.headCenter.x, this.headCenter.y - arrowRadius + this.arrowWidth * 3 / 2 - rate)

      ctx.fillStyle = 'rgb(255,255,255)'
      ctx.fill()
      ctx.strokeStyle = 'rgb(170,170,170)'
      ctx.stroke()
      ctx.restore()
    },
  },
}
</script>