index.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. import baseComponent from '../helpers/baseComponent'
  2. import classNames from '../helpers/libs/classNames'
  3. import { getSystemInfoSync } from '../helpers/hooks/useNativeAPI'
  4. import { getCanvasRef } from '../helpers/hooks/useCanvasAPI'
  5. const toAngle = (a) => a / 180 * Math.PI
  6. const percent = (a) => toAngle(a / 100 * 360)
  7. const easeInOutCubic = (a, b, c, d) => {
  8. a /= d / 2
  9. if (a < 1) return c / 2 * a * a * a + b
  10. a -= 2
  11. return c / 2 * (a * a * a + 2) + b
  12. }
  13. baseComponent({
  14. properties: {
  15. prefixCls: {
  16. type: String,
  17. value: 'wux-circle',
  18. },
  19. percent: {
  20. type: Number,
  21. value: 0,
  22. observer: 'redraw',
  23. },
  24. strokeWidth: {
  25. type: Number,
  26. value: 10,
  27. },
  28. size: {
  29. type: Number,
  30. value: 120,
  31. observer: 'updateStyle',
  32. },
  33. lineCap: {
  34. type: String,
  35. value: 'round',
  36. },
  37. backgroundColor: {
  38. type: String,
  39. value: '#f3f3f3',
  40. },
  41. color: {
  42. type: String,
  43. value: '#33cd5f',
  44. },
  45. sAngle: {
  46. type: Number,
  47. value: 0,
  48. observer(newVal) {
  49. this.setData({
  50. beginAngle: toAngle(newVal),
  51. })
  52. },
  53. },
  54. counterclockwise: {
  55. type: Boolean,
  56. value: false,
  57. },
  58. speed: {
  59. type: Number,
  60. value: 2000,
  61. },
  62. animate: {
  63. type: Boolean,
  64. value: true,
  65. },
  66. background: {
  67. type: Boolean,
  68. value: true,
  69. },
  70. },
  71. data: {
  72. beginAngle: 0,
  73. startAngle: 0,
  74. endAngle: 0,
  75. currentAngle: 0,
  76. },
  77. computed: {
  78. classes: ['prefixCls', function(prefixCls) {
  79. const wrap = classNames(prefixCls)
  80. const inner = `${prefixCls}__inner`
  81. return {
  82. wrap,
  83. inner,
  84. }
  85. }],
  86. },
  87. methods: {
  88. /**
  89. * 更新样式
  90. */
  91. updateStyle(size = this.data.size) {
  92. const style = `width: ${size}px; height: ${size}px;`
  93. this.setData({
  94. style,
  95. })
  96. },
  97. /**
  98. * 着帧绘制 canvas
  99. */
  100. redraw(value = this.data.percent) {
  101. const endAngle = percent(value)
  102. const now = Date.now()
  103. const decrease = this.data.currentAngle > endAngle
  104. const startAngle = !decrease ? this.data.currentAngle : this.data.endAngle
  105. this.cancelNextCallback()
  106. this.clearTimer()
  107. this.safeSetData({ startAngle, endAngle }, () => {
  108. this.animate(now, now, decrease)
  109. })
  110. },
  111. /**
  112. * 绘制 canvas
  113. */
  114. draw(hasLine = true) {
  115. const props = this.data
  116. const { lineCap, backgroundColor, color, size, strokeWidth, counterclockwise, background } = this.data
  117. const position = size / 2
  118. const radius = position - strokeWidth / 2
  119. const p = 2 * Math.PI
  120. const startAngle = counterclockwise ? p - props.beginAngle : props.beginAngle
  121. const endAngle = counterclockwise ? p - (props.beginAngle + props.currentAngle) : props.beginAngle + props.currentAngle
  122. // 创建 canvas 绘图上下文
  123. getCanvasRef(props.prefixCls, this).then((canvas) => {
  124. const ctx = canvas.getContext('2d')
  125. const ratio = getSystemInfoSync(['window']).pixelRatio
  126. const canvasWidth = size * ratio
  127. const canvasHeight = size * ratio
  128. canvas.width = canvasWidth
  129. canvas.height = canvasHeight
  130. ctx.scale(ratio, ratio)
  131. // ctx.fillStyle = '#ffffff'
  132. ctx.fillRect(0, 0, size, size)
  133. // 清除画布
  134. ctx.clearRect(0, 0, size, size)
  135. // 绘制背景
  136. if (background) {
  137. ctx.beginPath()
  138. ctx.arc(position, position, radius, 0, 2 * Math.PI)
  139. ctx.lineWidth = strokeWidth
  140. ctx.strokeStyle = backgroundColor
  141. ctx.stroke()
  142. }
  143. // 绘制进度
  144. if (hasLine) {
  145. ctx.beginPath()
  146. ctx.arc(position, position, radius, startAngle, endAngle)
  147. ctx.lineWidth = strokeWidth
  148. ctx.strokeStyle = color
  149. ctx.lineCap = lineCap
  150. ctx.stroke()
  151. }
  152. // 绘制完成
  153. this.triggerEvent('change', { value: this.data.currentAngle })
  154. })
  155. },
  156. /**
  157. * 开始动画
  158. */
  159. animate(c, d, e) {
  160. const now = Date.now()
  161. const f = now - c < 1 ? 1 : now - c
  162. const { animate, speed, startAngle, endAngle } = this.data
  163. const isEnd = !e && 1000 * this.data.currentAngle <= Math.floor(1000 * endAngle) || e && 1000 * this.data.currentAngle >= Math.floor(1000 * endAngle)
  164. if (animate && c - d < 1.05 * speed && isEnd) {
  165. const value = easeInOutCubic((c - d) / f, startAngle, endAngle - startAngle, speed / f)
  166. const currentAngle = value < 0 ? 0 : value
  167. c = Date.now()
  168. this.safeSetData({ currentAngle }, () => {
  169. this.draw(currentAngle !== 0)
  170. this.timer = setTimeout(() => this.animate(c, d, e), 1000 / 60)
  171. })
  172. } else {
  173. this.safeSetData({ currentAngle: endAngle }, () => this.draw(endAngle !== 0))
  174. }
  175. },
  176. /**
  177. * 清除定时器
  178. */
  179. clearTimer() {
  180. if (this.timer) {
  181. clearTimeout(this.timer)
  182. this.timer = null
  183. }
  184. },
  185. },
  186. attached() {
  187. this.updateStyle()
  188. if (this.data.percent === 0) {
  189. this.draw(false)
  190. }
  191. },
  192. detached() {
  193. this.clearTimer()
  194. },
  195. })