tui-circular-progress.vue 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. <template>
  2. <view class="tui-circular-container" :style="{ width: diam + 'px', height: (height || diam) + 'px' }">
  3. <canvas
  4. class="tui-circular-default"
  5. :canvas-id="defaultCanvasId"
  6. :id="defaultCanvasId"
  7. :style="{ width: diam + 'px', height: (height || diam) + 'px' }"
  8. v-if="defaultShow"
  9. ></canvas>
  10. <canvas class="tui-circular-progress" :canvas-id="progressCanvasId" :id="progressCanvasId" :style="{ width: diam + 'px', height: (height || diam) + 'px' }"></canvas>
  11. <slot />
  12. </view>
  13. </template>
  14. <script>
  15. export default {
  16. name: 'tuiCircularProgress',
  17. props: {
  18. /*
  19. 传值需使用rpx进行转换保证各终端兼容
  20. px = rpx / 750 * wx.getSystemInfoSync().windowWidth
  21. 圆形进度条(画布)宽度,直径 [px]
  22. */
  23. diam: {
  24. type: Number,
  25. default: 60
  26. },
  27. //圆形进度条(画布)高度,默认取diam值[当画半弧时传值,height有值时则取height]
  28. height: {
  29. type: Number,
  30. default: 0
  31. },
  32. //进度条线条宽度[px]
  33. lineWidth: {
  34. type: Number,
  35. default: 4
  36. },
  37. /*
  38. 线条的端点样式
  39. butt:向线条的每个末端添加平直的边缘
  40. round 向线条的每个末端添加圆形线帽
  41. square 向线条的每个末端添加正方形线帽
  42. */
  43. lineCap: {
  44. type: String,
  45. default: 'round'
  46. },
  47. //圆环进度字体大小 [px]
  48. fontSize: {
  49. type: Number,
  50. default: 12
  51. },
  52. //圆环进度字体颜色
  53. fontColor: {
  54. type: String,
  55. default: '#5677fc'
  56. },
  57. //是否显示进度文字
  58. fontShow: {
  59. type: Boolean,
  60. default: true
  61. },
  62. /*
  63. 自定义显示文字[默认为空,显示百分比,fontShow=true时生效]
  64. 可以使用 slot自定义显示内容
  65. */
  66. percentText: {
  67. type: String,
  68. default: ''
  69. },
  70. //是否显示默认(背景)进度条
  71. defaultShow: {
  72. type: Boolean,
  73. default: true
  74. },
  75. //默认进度条颜色
  76. defaultColor: {
  77. type: String,
  78. default: '#CCC'
  79. },
  80. //进度条颜色
  81. progressColor: {
  82. type: String,
  83. default: '#5677fc'
  84. },
  85. //进度条渐变颜色[结合progressColor使用,默认为空]
  86. gradualColor: {
  87. type: String,
  88. default: ''
  89. },
  90. //起始弧度,单位弧度
  91. sAngle: {
  92. type: Number,
  93. default: -Math.PI / 2
  94. },
  95. //指定弧度的方向是逆时针还是顺时针。默认是false,即顺时针
  96. counterclockwise: {
  97. type: Boolean,
  98. default: false
  99. },
  100. //进度百分比 [10% 传值 10]
  101. percentage: {
  102. type: Number,
  103. default: 0
  104. },
  105. //进度百分比缩放倍数[使用半弧为100%时,则可传2]
  106. multiple: {
  107. type: Number,
  108. default: 1
  109. },
  110. //动画执行时间[单位毫秒,低于50无动画]
  111. duration: {
  112. type: Number,
  113. default: 800
  114. },
  115. //backwards: 动画从头播;forwards:动画从上次结束点接着播
  116. activeMode: {
  117. type: String,
  118. default: 'backwards'
  119. }
  120. },
  121. watch: {
  122. percentage(val) {
  123. this.initDraw();
  124. }
  125. },
  126. data() {
  127. return {
  128. // #ifdef MP-WEIXIN
  129. progressCanvasId: 'progressCanvasId',
  130. defaultCanvasId: 'defaultCanvasId',
  131. // #endif
  132. // #ifndef MP-WEIXIN
  133. progressCanvasId: this.getCanvasId(),
  134. defaultCanvasId: this.getCanvasId(),
  135. // #endif
  136. progressContext: null,
  137. linearGradient: null,
  138. //起始百分比
  139. startPercentage: 0
  140. // dpi
  141. //pixelRatio: uni.getSystemInfoSync().pixelRatio
  142. };
  143. },
  144. mounted() {
  145. this.initDraw(true);
  146. },
  147. methods: {
  148. //初始化绘制
  149. initDraw(init) {
  150. let start = this.activeMode === 'backwards' ? 0 : this.startPercentage;
  151. start = start > this.percentage ? 0 : start;
  152. if (this.defaultShow && init) {
  153. this.drawDefaultCircular();
  154. }
  155. this.drawProgressCircular(start);
  156. },
  157. //默认(背景)圆环
  158. drawDefaultCircular() {
  159. let ctx = uni.createCanvasContext(this.defaultCanvasId, this);
  160. ctx.setLineWidth(this.lineWidth);
  161. ctx.setStrokeStyle(this.defaultColor);
  162. //终止弧度
  163. let eAngle = Math.PI * (this.height ? 1 : 2) + this.sAngle;
  164. this.drawArc(ctx, eAngle);
  165. },
  166. //进度圆环
  167. drawProgressCircular(startPercentage) {
  168. let ctx = this.progressContext;
  169. let gradient = this.linearGradient;
  170. if (!ctx) {
  171. ctx = uni.createCanvasContext(this.progressCanvasId, this);
  172. //创建一个线性的渐变颜色 CanvasGradient对象
  173. gradient = ctx.createLinearGradient(0, 0, this.diam, 0);
  174. gradient.addColorStop('0', this.progressColor);
  175. if (this.gradualColor) {
  176. gradient.addColorStop('1', this.gradualColor);
  177. }
  178. // #ifdef APP-PLUS
  179. const res = uni.getSystemInfoSync();
  180. if (!this.gradualColor && res.platform.toLocaleLowerCase() == 'android') {
  181. gradient.addColorStop('1', this.progressColor);
  182. }
  183. // #endif
  184. this.progressContext = ctx;
  185. this.linearGradient = gradient;
  186. }
  187. ctx.setLineWidth(this.lineWidth);
  188. ctx.setStrokeStyle(gradient);
  189. let time = this.percentage == 0 || this.duration < 50 ? 0 : this.duration / this.percentage;
  190. if (this.percentage > 0) {
  191. startPercentage = this.duration < 50 ? this.percentage - 1 : startPercentage;
  192. startPercentage++;
  193. }
  194. if (this.fontShow) {
  195. ctx.setFontSize(this.fontSize);
  196. ctx.setFillStyle(this.fontColor);
  197. ctx.setTextAlign('center');
  198. ctx.setTextBaseline('middle');
  199. let percentage = this.percentText;
  200. if (!percentage) {
  201. percentage = this.counterclockwise ? 100 - startPercentage * this.multiple : startPercentage * this.multiple;
  202. percentage = `${percentage}%`;
  203. }
  204. let radius = this.diam / 2;
  205. ctx.fillText(percentage, radius, radius);
  206. }
  207. if (this.percentage == 0 || (this.counterclockwise && startPercentage == 100)) {
  208. ctx.draw();
  209. }else{
  210. let eAngle = ((2 * Math.PI) / 100) * startPercentage + this.sAngle;
  211. this.drawArc(ctx, eAngle);
  212. }
  213. setTimeout(() => {
  214. this.startPercentage = startPercentage;
  215. if (startPercentage == this.percentage) {
  216. this.$emit('end', {
  217. canvasId: this.progressCanvasId,
  218. percentage: startPercentage
  219. });
  220. } else {
  221. this.drawProgressCircular(startPercentage);
  222. }
  223. this.$emit('change', {
  224. percentage: startPercentage
  225. });
  226. }, time);
  227. // #ifdef H5
  228. // requestAnimationFrame(()=>{})
  229. // #endif
  230. },
  231. //创建弧线
  232. drawArc(ctx, eAngle) {
  233. ctx.setLineCap(this.lineCap);
  234. ctx.beginPath();
  235. let radius = this.diam / 2; //x=y
  236. ctx.arc(radius, radius, radius - this.lineWidth, this.sAngle, eAngle, this.counterclockwise);
  237. ctx.stroke();
  238. ctx.draw();
  239. },
  240. //生成canvasId
  241. getCanvasId() {
  242. let uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
  243. return (c === 'x' ? (Math.random() * 16) | 0 : 'r&0x3' | '0x8').toString(16);
  244. });
  245. return uuid;
  246. }
  247. }
  248. };
  249. </script>
  250. <style scoped>
  251. .tui-circular-container,
  252. .tui-circular-default {
  253. position: relative;
  254. }
  255. .tui-circular-progress {
  256. position: absolute;
  257. left: 0;
  258. top: 0;
  259. z-index: 10;
  260. }
  261. </style>