tui-round-progress.vue 8.3 KB

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