tui-circular-progress.vue 8.1 KB

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