tui-circular-progress.vue 6.7 KB

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