<template> <view class="tn-circle-progress-class tn-circle-progress" :style="{ width: widthPx + 'px', height: widthPx + 'px' }" > <!-- 支付宝小程序不支持canvas-id属性,必须用id属性 --> <!-- 默认圆环 --> <canvas class="tn-circle-progress__canvas-bg" :canvas-id="elBgId" :id="elBgId" :style="{ width: widthPx + 'px', height: widthPx + 'px' }" ></canvas> <!-- 进度圆环 --> <canvas class="tn-circle-progress__canvas" :canvas-id="elId" :id="elId" :style="{ width: widthPx + 'px', height: widthPx + 'px' }" ></canvas> <view class="tn-circle-progress__content"> <slot v-if="$slots.default || $slots.$default"></slot> <view v-else-if="showPercent" class="tn-circle-progress__content__percent">{{ percent + '%' }}</view> </view> </view> </template> <script> export default { name: 'tn-circle-progress', props: { // 进度(百分比) percent: { type: Number, default: 0, validator: val => { return val >= 0 && val <= 100 } }, // 圆环线宽 borderWidth: { type: Number, default: 14 }, // 整体圆的宽度 width: { type: Number, default: 200 }, // 是否显示条纹 striped: { type: Boolean, default: false }, // 条纹是否运动 stripedActive: { type: Boolean, default: true }, // 激活部分颜色 activeColor: { type: String, default: '#01BEFF' }, // 非激活部分颜色 inactiveColor: { type: String, default: '#f0f0f0' }, // 是否显示进度条内部百分比值 showPercent: { type: Boolean, default: false }, // 圆环执行动画的时间,ms duration: { type: Number, default: 1500 } }, data() { return { // 微信小程序中不能使用this.$t.uuid()形式动态生成id值,否则会报错 // #ifdef MP-WEIXIN elBgId: 'tCircleProgressBgId', elId: 'tCircleProgressElId', // #endif // #ifndef MP-WEIXIN elBgId: this.$t.uuid(), elId: this.$t.uuid(), // #endif // 活动圆上下文 progressContext: null, // 转换成px为单位的背景宽度 widthPx: uni.upx2px(this.width || 200), // 转换成px为单位的圆环宽度 borderWidthPx: uni.upx2px(this.borderWidth || 14), // canvas画圆的起始角度,默认为-90度,顺时针 startAngle: -90 * Math.PI / 180, // 动态修改进度值的时候,保存进度值的变化前后值 newPercent: 0, oldPercent: 0 } }, watch: { percent(newVal, oldVal = 0) { if (newVal > 100) newVal = 100 if (oldVal < 0) oldVal = 0 this.newPercent = newVal this.oldPercent = oldVal setTimeout(() => { // 无论是百分比值增加还是减少,需要操作还是原来的旧的百分比值 // 将此值减少或者新增到新的百分比值 this.drawCircleByProgress(oldVal) }, 50) } }, created() { // 赋值,用于加载后第一个画圆使用 this.newPercent = this.percent; this.oldPercent = 0; }, mounted() { setTimeout(() => { this.drawProgressBg() this.drawCircleByProgress(this.oldPercent) }, 50) }, methods: { // 绘制进度条背景 drawProgressBg() { let ctx = uni.createCanvasContext(this.elBgId, this) // 设置线宽 ctx.setLineWidth(this.borderWidthPx) // 设置颜色 ctx.setStrokeStyle(this.inactiveColor) ctx.beginPath() let radius = this.widthPx / 2 ctx.arc(radius, radius, radius - this.borderWidthPx, 0, 360 * Math.PI / 180, false) ctx.stroke() ctx.draw() }, // 绘制圆弧的进度 drawCircleByProgress(progress) { // 如果已经存在则拿来使用 let ctx = this.progressContext if (!ctx) { ctx =uni.createCanvasContext(this.elId, this) this.progressContext = ctx } ctx.setLineCap('round') // 设置线条宽度和颜色 ctx.setLineWidth(this.borderWidthPx) ctx.setStrokeStyle(this.activeColor) // 将总过渡时间除以100,得出每修改百分之一进度所需的时间 let preSecondTime = Math.floor(this.duration / 100) // 结束角的计算依据为:将2π分为100份,乘以当前的进度值,得出终止点的弧度值,加起始角,为整个圆从默认的 let endAngle = ((360 * Math.PI / 180) / 100) * progress + this.startAngle let radius = this.widthPx / 2 ctx.beginPath() ctx.arc(radius, radius, radius - this.borderWidthPx, this.startAngle, endAngle, false) ctx.stroke() ctx.draw() // 如果变更后新值大于旧值,意味着增大了百分比 if (this.newPercent > this.oldPercent) { // 每次递增百分之一 progress++ // 如果新增后的值,大于需要设置的值百分比值,停止继续增加 if (progress > this.newPercent) return } else { progress-- if (progress < this.newPercent) return } setTimeout(() => { // 定时器,每次操作间隔为time值,为了让进度条有动画效果 this.drawCircleByProgress(progress) }, preSecondTime) } } } </script> <style lang="scss" scoped> .tn-circle-progress { position: relative; /* #ifndef APP-NVUE */ display: inline-flex; /* #endif */ align-items: center; justify-content: center; background-color: transparent; &__canvas { position: absolute; &-bg { position: absolute; } } &__content { display: flex; align-items: center; justify-content: center; &__percent { font-size: 28rpx; } } } </style>