tui-round-progress.vue 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. <template>
  2. <view class="tui-circular-container" :style="{ width: diam + 'px', height: (height || diam) + 'px' }">
  3. <canvas :start="percent" :change:start="parse.initDraw" :data-width="diam" :data-height="height"
  4. :data-lineWidth="lineWidth" :data-lineCap="lineCap" :data-fontSize="fontSize" :data-fontColor="fontColor"
  5. :data-fontShow="fontShow" :data-percentText="percentText" :data-defaultShow="defaultShow"
  6. :data-defaultColor="defaultColor" :data-progressColor="progressColor" :data-gradualColor="gradualColor"
  7. :data-sAngle="sAngle" :data-counterclockwise="counterclockwise" :data-multiple="multiple"
  8. :data-speed="speed" :data-activeMode="activeMode" :data-cid="progressCanvasId" :canvas-id="progressCanvasId"
  9. :class="[progressCanvasId]" :style="{ width: diam + 'px', height: (height || diam) + 'px' }"></canvas>
  10. <slot></slot>
  11. </view>
  12. </template>
  13. <script module="parse" lang="renderjs">
  14. export default {
  15. methods: {
  16. format(str) {
  17. if (!str) return str;
  18. return str.replace(/\"/g, "");
  19. },
  20. bool(str) {
  21. return str === 'true' || str == true ? true : false
  22. },
  23. //初始化绘制
  24. initDraw(percentage, oldPercentage, owner, ins) {
  25. let state = ins.getState();
  26. let res = ins.getDataset();
  27. const activeMode = this.format(res.activemode);
  28. let start = activeMode === 'backwards' ? 0 : (state.startPercentage || 0);
  29. //当start大于当前percentage时,start设置为0
  30. start = start > percentage ? 0 : start;
  31. if (!state.progressContext || !state.canvas) {
  32. const width = res.width;
  33. const height = res.height == 0 ? res.width : res.height;
  34. let ele = `.${res.cid}>canvas`
  35. const canvas = document.querySelectorAll(this.format(ele))[0];
  36. const ctx = canvas.getContext('2d');
  37. // const dpr =uni.getSystemInfoSync().pixelRatio;
  38. // canvas.style.width=width+'px';
  39. // canvas.style.height=height+'px';
  40. // canvas.width = width * dpr;
  41. // canvas.height = height * dpr;
  42. // ctx.scale(dpr, dpr);
  43. state.progressContext = ctx;
  44. state.canvas = canvas;
  45. this.drawProgressCircular(start, ctx, canvas, percentage, res, state, owner);
  46. } else {
  47. this.drawProgressCircular(start, state.progressContext, state.canvas, percentage, res, state, owner);
  48. }
  49. },
  50. //默认(背景)圆环
  51. drawDefaultCircular(ctx, canvas, res) {
  52. //终止弧度
  53. let sangle = Number(res.sangle) * Math.PI
  54. let eAngle = Math.PI * (res.height != 0 ? 1 : 2) + sangle;
  55. this.drawArc(ctx, eAngle, this.format(res.defaultcolor), res);
  56. },
  57. drawPercentage(ctx, percentage, res) {
  58. ctx.save(); //save和restore可以保证样式属性只运用于该段canvas元素
  59. ctx.beginPath();
  60. ctx.fillStyle = this.format(res.fontcolor);
  61. ctx.font = res.fontsize + "px Arial"; //设置字体大小和字体
  62. ctx.textAlign = "center";
  63. ctx.textBaseline = "middle";
  64. let radius = res.width / 2;
  65. let percenttext = this.format(res.percenttext)
  66. if (!percenttext) {
  67. let multiple = Number(res.multiple)
  68. percentage = this.bool(res.counterclockwise) ? 100 - percentage * multiple : percentage * multiple;
  69. percentage = percentage.toFixed(0) + "%"
  70. } else {
  71. percentage = percenttext
  72. }
  73. ctx.fillText(percentage, radius, radius);
  74. ctx.stroke();
  75. ctx.restore();
  76. },
  77. //进度圆环
  78. drawProgressCircular(startPercentage, ctx, canvas, percentage, res, state, owner) {
  79. if (!ctx || !canvas) return;
  80. let that = this
  81. let gradient = ctx.createLinearGradient(0, 0, Number(res.width), 0);
  82. gradient.addColorStop(0, this.format(res.progresscolor));
  83. let gradualColor = this.format(res.gradualcolor)
  84. if (gradualColor) {
  85. gradient.addColorStop('1', gradualColor);
  86. }
  87. let requestId = null
  88. let renderLoop = () => {
  89. drawFrame((res) => {
  90. if (res) {
  91. requestId = requestAnimationFrame(renderLoop)
  92. } else {
  93. setTimeout(() => {
  94. cancelAnimationFrame(requestId)
  95. requestId = null;
  96. renderLoop = null;
  97. }, 20)
  98. }
  99. })
  100. }
  101. renderLoop()
  102. // requestId = requestAnimationFrame(renderLoop)
  103. function drawFrame(callback) {
  104. ctx.clearRect(0, 0, canvas.width, canvas.height);
  105. if (that.bool(res.defaultshow)) {
  106. that.drawDefaultCircular(ctx, canvas, res)
  107. }
  108. if (that.bool(res.fontshow)) {
  109. that.drawPercentage(ctx, startPercentage, res);
  110. }
  111. let isEnd = percentage === 0 || (that.bool(res.counterclockwise) && startPercentage === 100);
  112. if (!isEnd) {
  113. let sangle = Number(res.sangle) * Math.PI
  114. let eAngle = ((2 * Math.PI) / 100) * startPercentage + sangle;
  115. that.drawArc(ctx, eAngle, gradient, res);
  116. }
  117. owner.callMethod('change', {
  118. percentage: startPercentage
  119. })
  120. if (startPercentage >= percentage) {
  121. state.startPercentage = startPercentage;
  122. owner.callMethod('end', {
  123. canvasId: that.format(res.canvasid)
  124. })
  125. callback && callback(false)
  126. } else {
  127. let num = startPercentage + Number(res.speed)
  128. startPercentage = num > percentage ? percentage : num;
  129. callback && callback(true)
  130. }
  131. }
  132. },
  133. //创建弧线
  134. drawArc(ctx, eAngle, strokeStyle, res) {
  135. ctx.save();
  136. ctx.beginPath();
  137. ctx.lineCap = this.format(res.linecap);
  138. ctx.lineWidth = Number(res.linewidth);
  139. ctx.strokeStyle = strokeStyle;
  140. let radius = res.width / 2; //x=y
  141. let sangle = Number(res.sangle) * Math.PI
  142. ctx.arc(radius, radius, radius - res.linewidth, sangle, eAngle, this.bool(res.counterclockwise));
  143. ctx.stroke();
  144. ctx.closePath();
  145. ctx.restore();
  146. }
  147. }
  148. }
  149. </script>
  150. <script>
  151. export default {
  152. name: 'tuiRoundProgress',
  153. emits: ['change','end'],
  154. props: {
  155. /*
  156. 传值需使用rpx进行转换保证各终端兼容
  157. px = rpx / 750 * wx.getSystemInfoSync().windowWidth
  158. 圆形进度条(画布)宽度,直径 [px]
  159. */
  160. diam: {
  161. type: Number,
  162. default: 60
  163. },
  164. //圆形进度条(画布)高度,默认取diam值[当画半弧时传值,height有值时则取height]
  165. height: {
  166. type: Number,
  167. default: 0
  168. },
  169. //进度条线条宽度[px]
  170. lineWidth: {
  171. type: Number,
  172. default: 4
  173. },
  174. /*
  175. 线条的端点样式
  176. butt:向线条的每个末端添加平直的边缘
  177. round 向线条的每个末端添加圆形线帽
  178. square 向线条的每个末端添加正方形线帽
  179. */
  180. lineCap: {
  181. type: String,
  182. default: 'round'
  183. },
  184. //圆环进度字体大小 [px]
  185. fontSize: {
  186. type: Number,
  187. default: 12
  188. },
  189. //圆环进度字体颜色
  190. fontColor: {
  191. type: String,
  192. default: '#5677fc'
  193. },
  194. //是否显示进度文字
  195. fontShow: {
  196. type: Boolean,
  197. default: true
  198. },
  199. /*
  200. 自定义显示文字[默认为空,显示百分比,fontShow=true时生效]
  201. 可以使用 slot自定义显示内容
  202. */
  203. percentText: {
  204. type: String,
  205. default: ''
  206. },
  207. //是否显示默认(背景)进度条
  208. defaultShow: {
  209. type: Boolean,
  210. default: true
  211. },
  212. //默认进度条颜色
  213. defaultColor: {
  214. type: String,
  215. default: '#CCC'
  216. },
  217. //进度条颜色
  218. progressColor: {
  219. type: String,
  220. default: '#5677fc'
  221. },
  222. //进度条渐变颜色[结合progressColor使用,默认为空]
  223. gradualColor: {
  224. type: String,
  225. default: ''
  226. },
  227. //起始弧度,单位弧度 实际 Math.PI * sAngle
  228. sAngle: {
  229. type: Number,
  230. default: -0.5
  231. },
  232. //指定弧度的方向是逆时针还是顺时针。默认是false,即顺时针
  233. counterclockwise: {
  234. type: Boolean,
  235. default: false
  236. },
  237. //进度百分比 [10% 传值 10]
  238. percentage: {
  239. type: Number,
  240. default: 0
  241. },
  242. //进度百分比缩放倍数[使用半弧为100%时,则可传2]
  243. multiple: {
  244. type: Number,
  245. default: 1
  246. },
  247. //动画执行速度,值越大动画越快(0.1~100)
  248. speed: {
  249. type: [Number, String],
  250. default: 1
  251. },
  252. //backwards: 动画从头播;forwards:动画从上次结束点接着播
  253. activeMode: {
  254. type: String,
  255. default: 'backwards'
  256. }
  257. },
  258. watch: {
  259. percentage(val) {
  260. this.percent = val;
  261. }
  262. },
  263. mounted() {
  264. setTimeout(() => {
  265. this.percent = this.percentage;
  266. }, 50);
  267. },
  268. data() {
  269. return {
  270. percent: -1,
  271. progressCanvasId: this.getCanvasId()
  272. };
  273. },
  274. methods: {
  275. getCanvasId() {
  276. return 'tui' + new Date().getTime() + (Math.random() * 100000).toFixed(0);
  277. },
  278. change(e) {
  279. //绘制进度
  280. this.$emit('change', e);
  281. },
  282. end(e) {
  283. //绘制完成
  284. this.$emit('end', e);
  285. }
  286. }
  287. };
  288. </script>
  289. <style scoped>
  290. .tui-circular-container {
  291. position: relative;
  292. }
  293. </style>