tui-round-progress.vue 8.84 KB
<template>
	<view class="tui-circular-container" :style="{ width: diam + 'px', height: (height || diam) + 'px' }">
		<canvas :start="percent" :change:start="parse.initDraw" :data-width="diam" :data-height="height"
			:data-lineWidth="lineWidth" :data-lineCap="lineCap" :data-fontSize="fontSize" :data-fontColor="getFontColor"
			:data-fontShow="fontShow" :data-percentText="percentText" :data-defaultShow="defaultShow"
			:data-defaultColor="defaultColor" :data-progressColor="getProgressColor" :data-gradualColor="gradualColor"
			:data-sAngle="sAngle" :data-counterclockwise="counterclockwise" :data-multiple="multiple"
			:data-speed="speed" :data-activeMode="activeMode" :data-cid="progressCanvasId" :canvas-id="progressCanvasId"
			:class="[progressCanvasId]" :style="{ width: diam + 'px', height: (height || diam) + 'px' }"></canvas>
		<slot></slot>
	</view>
</template>
<script module="parse" lang="renderjs">
	export default {
		methods: {
			format(str) {
				if (!str) return str;
				return str.replace(/\"/g, "");
			},
			bool(str) {
				return str === 'true' || str == true ? true : false
			},
			//初始化绘制
			initDraw(percentage, oldPercentage, owner, ins) {
				let state = ins.getState();
				let res = ins.getDataset();
				const activeMode = this.format(res.activemode);
				let start = activeMode === 'backwards' ? 0 : (state.startPercentage || 0);
				//当start大于当前percentage时,start设置为0
				start = start > percentage ? 0 : start;
				if (!state.progressContext || !state.canvas) {
					const width = res.width;
					const height = res.height == 0 ? res.width : res.height;
					let ele = `.${res.cid}>canvas`
					const canvas = document.querySelectorAll(this.format(ele))[0];
					if (!canvas) return;
					const ctx = canvas.getContext('2d');
					// const dpr =uni.getSystemInfoSync().pixelRatio;
					// canvas.style.width=width+'px';
					// canvas.style.height=height+'px';
					// canvas.width = width * dpr;
					// canvas.height = height * dpr;
					// ctx.scale(dpr, dpr);
					state.progressContext = ctx;
					state.canvas = canvas;
					this.drawProgressCircular(start, ctx, canvas, percentage, res, state, owner);
				} else {
					this.drawProgressCircular(start, state.progressContext, state.canvas, percentage, res, state, owner);
				}
			},
			//默认(背景)圆环
			drawDefaultCircular(ctx, canvas, res) {
				//终止弧度
				let sangle = Number(res.sangle) * Math.PI
				let eAngle = Math.PI * (res.height != 0 ? 1 : 2) + sangle;
				this.drawArc(ctx, eAngle, this.format(res.defaultcolor), res);
			},
			drawPercentage(ctx, percentage, res) {
				ctx.save(); //save和restore可以保证样式属性只运用于该段canvas元素
				ctx.beginPath();
				ctx.fillStyle = this.format(res.fontcolor);
				ctx.font = res.fontsize + "px Arial"; //设置字体大小和字体
				ctx.textAlign = "center";
				ctx.textBaseline = "middle";
				let radius = res.width / 2;
				let percenttext = this.format(res.percenttext)
				if (!percenttext) {
					let multiple = Number(res.multiple)
					percentage = this.bool(res.counterclockwise) ? 100 - percentage * multiple : percentage * multiple;
					percentage = percentage.toFixed(0) + "%"
				} else {
					percentage = percenttext
				}
				ctx.fillText(percentage, radius, radius);
				ctx.stroke();
				ctx.restore();
			},
			//进度圆环
			drawProgressCircular(startPercentage, ctx, canvas, percentage, res, state, owner) {
				if (!ctx || !canvas) return;
				let that = this
				let gradient = ctx.createLinearGradient(0, 0, Number(res.width), 0);
				gradient.addColorStop(0, this.format(res.progresscolor));
				let gradualColor = this.format(res.gradualcolor)
				if (gradualColor) {
					gradient.addColorStop('1', gradualColor);
				}
				let requestId = null
				let renderLoop = () => {
					drawFrame((res) => {
						if (res) {
							requestId = requestAnimationFrame(renderLoop)
						} else {
							setTimeout(() => {
								cancelAnimationFrame(requestId)
								requestId = null;
								renderLoop = null;
							}, 20)
						}
					})
				}
				renderLoop()
				// requestId = requestAnimationFrame(renderLoop)

				function drawFrame(callback) {
					ctx.clearRect(0, 0, canvas.width, canvas.height);
					if (that.bool(res.defaultshow)) {
						that.drawDefaultCircular(ctx, canvas, res)
					}
					if (that.bool(res.fontshow)) {
						that.drawPercentage(ctx, startPercentage, res);
					}
					let isEnd = percentage === 0 || (that.bool(res.counterclockwise) && startPercentage === 100);
					if (!isEnd) {
						let sangle = Number(res.sangle) * Math.PI
						let eAngle = ((2 * Math.PI) / 100) * startPercentage + sangle;
						that.drawArc(ctx, eAngle, gradient, res);
					}
					owner.callMethod('change', {
						percentage: startPercentage
					})
					if (startPercentage >= percentage) {
						state.startPercentage = startPercentage;
						owner.callMethod('end', {
							canvasId: that.format(res.canvasid)
						})
						callback && callback(false)
					} else {
						let num = startPercentage + Number(res.speed)
						startPercentage = num > percentage ? percentage : num;
						callback && callback(true)
					}
				}

			},
			//创建弧线
			drawArc(ctx, eAngle, strokeStyle, res) {
				ctx.save();
				ctx.beginPath();
				ctx.lineCap = this.format(res.linecap);
				ctx.lineWidth = Number(res.linewidth);
				ctx.strokeStyle = strokeStyle;
				let radius = res.width / 2; //x=y
				let sangle = Number(res.sangle) * Math.PI
				ctx.arc(radius, radius, radius - res.linewidth, sangle, eAngle, this.bool(res.counterclockwise));
				ctx.stroke();
				ctx.closePath();
				ctx.restore();
			}
		}
	}
</script>
<script>
	export default {
		name: 'tuiRoundProgress',
		emits: ['change', 'end'],
		props: {
			/*
			  传值需使用rpx进行转换保证各终端兼容
			  px = rpx / 750 * wx.getSystemInfoSync().windowWidth
			  圆形进度条(画布)宽度,直径 [px]
			*/
			diam: {
				type: Number,
				default: 60
			},
			//圆形进度条(画布)高度,默认取diam值[当画半弧时传值,height有值时则取height]
			height: {
				type: Number,
				default: 0
			},
			//进度条线条宽度[px]
			lineWidth: {
				type: Number,
				default: 4
			},
			/*
				 线条的端点样式
				 butt:向线条的每个末端添加平直的边缘
				 round	向线条的每个末端添加圆形线帽
				 square	向线条的每个末端添加正方形线帽
				*/
			lineCap: {
				type: String,
				default: 'round'
			},
			//圆环进度字体大小 [px]
			fontSize: {
				type: Number,
				default: 12
			},
			//圆环进度字体颜色
			fontColor: {
				type: String,
				default: ''
			},
			//是否显示进度文字
			fontShow: {
				type: Boolean,
				default: true
			},
			/*
				 自定义显示文字[默认为空,显示百分比,fontShow=true时生效]
				 可以使用 slot自定义显示内容
				*/
			percentText: {
				type: String,
				default: ''
			},
			//是否显示默认(背景)进度条
			defaultShow: {
				type: Boolean,
				default: true
			},
			//默认进度条颜色
			defaultColor: {
				type: String,
				default: '#CCC'
			},
			//进度条颜色
			progressColor: {
				type: String,
				default: ''
			},
			//进度条渐变颜色[结合progressColor使用,默认为空]
			gradualColor: {
				type: String,
				default: ''
			},
			//起始弧度,单位弧度 实际  Math.PI * sAngle
			sAngle: {
				type: Number,
				default: -0.5
			},
			//指定弧度的方向是逆时针还是顺时针。默认是false,即顺时针
			counterclockwise: {
				type: Boolean,
				default: false
			},
			//进度百分比 [10% 传值 10]
			percentage: {
				type: Number,
				default: 0
			},
			//进度百分比缩放倍数[使用半弧为100%时,则可传2]
			multiple: {
				type: Number,
				default: 1
			},
			//动画执行速度,值越大动画越快(0.1~100)
			speed: {
				type: [Number, String],
				default: 1
			},
			//backwards: 动画从头播;forwards:动画从上次结束点接着播
			activeMode: {
				type: String,
				default: 'backwards'
			}
		},
		computed: {
			getFontColor() {
				return this.fontColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
			},
			getProgressColor() {
				return this.progressColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
			}
		},
		watch: {
			percentage(val) {
				if (this.isInit) {
					this.percent = val;
				}
			}
		},
		mounted() {
			setTimeout(() => {
				this.percent = this.percentage;
				this.isInit = true;
			}, 50);
		},
		data() {
			return {
				isInit: false,
				percent: -1,
				progressCanvasId: this.getCanvasId()
			};
		},
		methods: {
			getCanvasId() {
				return 'tui' + new Date().getTime() + (Math.random() * 100000).toFixed(0);
			},
			change(e) {
				//绘制进度
				this.$emit('change', e);
			},
			end(e) {
				//绘制完成
				this.$emit('end', e);
			}
		}
	};
</script>

<style scoped>
	.tui-circular-container {
		position: relative;
	}
</style>