tn-circle-progress.vue 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. <template>
  2. <view
  3. class="tn-circle-progress-class tn-circle-progress"
  4. :style="{
  5. width: widthPx + 'px',
  6. height: widthPx + 'px'
  7. }"
  8. >
  9. <!-- 支付宝小程序不支持canvas-id属性,必须用id属性 -->
  10. <!-- 默认圆环 -->
  11. <canvas
  12. class="tn-circle-progress__canvas-bg"
  13. :canvas-id="elBgId"
  14. :id="elBgId"
  15. :style="{
  16. width: widthPx + 'px',
  17. height: widthPx + 'px'
  18. }"
  19. ></canvas>
  20. <!-- 进度圆环 -->
  21. <canvas
  22. class="tn-circle-progress__canvas"
  23. :canvas-id="elId"
  24. :id="elId"
  25. :style="{
  26. width: widthPx + 'px',
  27. height: widthPx + 'px'
  28. }"
  29. ></canvas>
  30. <view class="tn-circle-progress__content">
  31. <slot v-if="$slots.default || $slots.$default"></slot>
  32. <view v-else-if="showPercent" class="tn-circle-progress__content__percent">{{ percent + '%' }}</view>
  33. </view>
  34. </view>
  35. </template>
  36. <script>
  37. export default {
  38. name: 'tn-circle-progress',
  39. props: {
  40. // 进度(百分比)
  41. percent: {
  42. type: Number,
  43. default: 0,
  44. validator: val => {
  45. return val >= 0 && val <= 100
  46. }
  47. },
  48. // 圆环线宽
  49. borderWidth: {
  50. type: Number,
  51. default: 14
  52. },
  53. // 整体圆的宽度
  54. width: {
  55. type: Number,
  56. default: 200
  57. },
  58. // 是否显示条纹
  59. striped: {
  60. type: Boolean,
  61. default: false
  62. },
  63. // 条纹是否运动
  64. stripedActive: {
  65. type: Boolean,
  66. default: true
  67. },
  68. // 激活部分颜色
  69. activeColor: {
  70. type: String,
  71. default: '#01BEFF'
  72. },
  73. // 非激活部分颜色
  74. inactiveColor: {
  75. type: String,
  76. default: '#f0f0f0'
  77. },
  78. // 是否显示进度条内部百分比值
  79. showPercent: {
  80. type: Boolean,
  81. default: false
  82. },
  83. // 圆环执行动画的时间,ms
  84. duration: {
  85. type: Number,
  86. default: 1500
  87. }
  88. },
  89. data() {
  90. return {
  91. // 微信小程序中不能使用this.$t.uuid()形式动态生成id值,否则会报错
  92. // #ifdef MP-WEIXIN
  93. elBgId: 'tCircleProgressBgId',
  94. elId: 'tCircleProgressElId',
  95. // #endif
  96. // #ifndef MP-WEIXIN
  97. elBgId: this.$t.uuid(),
  98. elId: this.$t.uuid(),
  99. // #endif
  100. // 活动圆上下文
  101. progressContext: null,
  102. // 转换成px为单位的背景宽度
  103. widthPx: uni.upx2px(this.width || 200),
  104. // 转换成px为单位的圆环宽度
  105. borderWidthPx: uni.upx2px(this.borderWidth || 14),
  106. // canvas画圆的起始角度,默认为-90度,顺时针
  107. startAngle: -90 * Math.PI / 180,
  108. // 动态修改进度值的时候,保存进度值的变化前后值
  109. newPercent: 0,
  110. oldPercent: 0
  111. }
  112. },
  113. watch: {
  114. percent(newVal, oldVal = 0) {
  115. if (newVal > 100) newVal = 100
  116. if (oldVal < 0) oldVal = 0
  117. this.newPercent = newVal
  118. this.oldPercent = oldVal
  119. setTimeout(() => {
  120. // 无论是百分比值增加还是减少,需要操作还是原来的旧的百分比值
  121. // 将此值减少或者新增到新的百分比值
  122. this.drawCircleByProgress(oldVal)
  123. }, 50)
  124. }
  125. },
  126. created() {
  127. // 赋值,用于加载后第一个画圆使用
  128. this.newPercent = this.percent;
  129. this.oldPercent = 0;
  130. },
  131. mounted() {
  132. setTimeout(() => {
  133. this.drawProgressBg()
  134. this.drawCircleByProgress(this.oldPercent)
  135. }, 50)
  136. },
  137. methods: {
  138. // 绘制进度条背景
  139. drawProgressBg() {
  140. let ctx = uni.createCanvasContext(this.elBgId, this)
  141. // 设置线宽
  142. ctx.setLineWidth(this.borderWidthPx)
  143. // 设置颜色
  144. ctx.setStrokeStyle(this.inactiveColor)
  145. ctx.beginPath()
  146. let radius = this.widthPx / 2
  147. ctx.arc(radius, radius, radius - this.borderWidthPx, 0, 360 * Math.PI / 180, false)
  148. ctx.stroke()
  149. ctx.draw()
  150. },
  151. // 绘制圆弧的进度
  152. drawCircleByProgress(progress) {
  153. // 如果已经存在则拿来使用
  154. let ctx = this.progressContext
  155. if (!ctx) {
  156. ctx =uni.createCanvasContext(this.elId, this)
  157. this.progressContext = ctx
  158. }
  159. ctx.setLineCap('round')
  160. // 设置线条宽度和颜色
  161. ctx.setLineWidth(this.borderWidthPx)
  162. ctx.setStrokeStyle(this.activeColor)
  163. // 将总过渡时间除以100,得出每修改百分之一进度所需的时间
  164. let preSecondTime = Math.floor(this.duration / 100)
  165. // 结束角的计算依据为:将2π分为100份,乘以当前的进度值,得出终止点的弧度值,加起始角,为整个圆从默认的
  166. let endAngle = ((360 * Math.PI / 180) / 100) * progress + this.startAngle
  167. let radius = this.widthPx / 2
  168. ctx.beginPath()
  169. ctx.arc(radius, radius, radius - this.borderWidthPx, this.startAngle, endAngle, false)
  170. ctx.stroke()
  171. ctx.draw()
  172. // 如果变更后新值大于旧值,意味着增大了百分比
  173. if (this.newPercent > this.oldPercent) {
  174. // 每次递增百分之一
  175. progress++
  176. // 如果新增后的值,大于需要设置的值百分比值,停止继续增加
  177. if (progress > this.newPercent) return
  178. } else {
  179. progress--
  180. if (progress < this.newPercent) return
  181. }
  182. setTimeout(() => {
  183. // 定时器,每次操作间隔为time值,为了让进度条有动画效果
  184. this.drawCircleByProgress(progress)
  185. }, preSecondTime)
  186. }
  187. }
  188. }
  189. </script>
  190. <style lang="scss" scoped>
  191. .tn-circle-progress {
  192. position: relative;
  193. /* #ifndef APP-NVUE */
  194. display: inline-flex;
  195. /* #endif */
  196. align-items: center;
  197. justify-content: center;
  198. background-color: transparent;
  199. &__canvas {
  200. position: absolute;
  201. &-bg {
  202. position: absolute;
  203. }
  204. }
  205. &__content {
  206. display: flex;
  207. align-items: center;
  208. justify-content: center;
  209. &__percent {
  210. font-size: 28rpx;
  211. }
  212. }
  213. }
  214. </style>