Uniapp编写微信小程序,绘制动态圆环进度条

一、完整代码:

javascript 复制代码
<template>
	<view class="home">
		<view>
			<!-- 标题tab -->
			<view class="title">我是标题</view>
		</view>
		<scroll-view scroll-y style="height: calc(100vh - 360rpx)">
			<view class="chartBox">
				<view class="chartBox-progress">
					<canvas canvas-id="progressCanvas"
						:style="{width: canvasSize +iconSize*2 + 'px', height: canvasSize +iconSize*2 + 'px'}"></canvas>
				</view>
				<view class="content">
					<view class="content-value">
						<view class="value">{{info.progress}}</view>
						<view class="text">%</view>
					</view>
					<view class="content-tips">
						<view class="text">预计还需</view>
						<view class="value">{{info.remainingTime}}分钟</view>
					</view>
				</view>
			</view>
		</scroll-view>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				ctx: null,
				iconImage: '/static/monitor/cg.png',
				canvasSize: 208,
				iconSize: 34,
				ringWidth: 9,
				imagePath: '',
				imageLoaded: false,
				info: {
					progress: 0, // 初始化进度
					totalTime: 60, // 总时间(分钟)
					remainingTime: 60, // 剩余时间
				}
			}
		},
		watch: { // 监听进度变化,重绘圆环
			progress() {
				this.drawProgress();
			}
		},
		components: {},
		onLoad() {
			this.initCanvas();
			this.loadImage();
			this.simulateCharging();
		},
		methods: {
			initCanvas() {
				// 获取Canvas上下文
				this.ctx = uni.createCanvasContext('progressCanvas', this);
			},
			loadImage() {
				// 检查是否为本地资源
				if (this.iconImage.startsWith('/') || this.iconImage.startsWith('./')) {
					// 本地资源直接使用
					this.imagePath = this.iconImage;
					this.imageLoaded = true;
					this.drawProgress();
				} else {
					// 网络资源需要先下载
					uni.getImageInfo({
						src: this.iconImage,
						success: (res) => {
							this.imagePath = res.path;
							this.imageLoaded = true;
							this.drawProgress();
						},
						fail: (err) => {
							console.error('获取图片信息失败:', err);
							this.drawProgress();
						}
					});
				}
			},

			drawProgress() {
				// 计算总画布尺寸(包含图标空间)
				const totalSize = this.canvasSize + this.iconSize * 2;
				// 计算真正的中心点
				const center = totalSize / 2;
				// 计算圆环半径
				const radius = (this.canvasSize - this.ringWidth) / 2;

				// 清空画布
				this.ctx.clearRect(0, 0, totalSize, totalSize+ this.iconSize);

				// 绘制背景圆环
				this.drawInnerShadow(this.ctx, center, radius, this.ringWidth);
				this.ctx.beginPath();
				this.ctx.arc(center, center - this.iconSize / 2, radius, 0, Math.PI * 2);
				this.ctx.setStrokeStyle('#ffd1bf');
				this.ctx.setLineWidth(this.ringWidth);
				this.ctx.stroke();

				// 绘制进度圆环
				this.ctx.beginPath();
				const startAngle = -Math.PI / 2; // 从12点位置开始
				const endAngle = startAngle + (this.info.progress / 100) * (Math.PI * 2);
				this.ctx.arc(center, center - this.iconSize / 2, radius, startAngle, endAngle);
				this.ctx.setStrokeStyle('#FF6D33');
				this.ctx.setLineWidth(this.ringWidth);
				this.ctx.stroke();

				// 计算图标位置
				const iconAngle = startAngle + (this.info.progress / 100) * (Math.PI * 2);
				const iconX = center + Math.cos(iconAngle) * radius;
				const iconY = center + Math.sin(iconAngle) * radius;

				this.ctx.save();

				if (this.imageLoaded) {
					// 绘制图标(修正坐标计算)
					this.ctx.drawImage(
						this.imagePath,
						iconX - this.iconSize / 2, // 左顶点X = 中心点X - 图标宽度/2
						iconY - this.iconSize, // 上顶点Y = 中心点Y - 图标高度/2
						this.iconSize,
						this.iconSize
					);
				} else {
					// 图标加载失败时显示默认图标
					this.ctx.beginPath();
					this.ctx.arc(iconX, iconY, this.iconSize / 2, 0, Math.PI * 2);
					this.ctx.setFillStyle('#FF6D33');
					this.ctx.fill();
				}

				this.ctx.restore();
				this.ctx.draw();

				// 更新剩余时间
				this.info.remainingTime = Math.round((100 - this.info.progress) * this.info.totalTime / 100);
			},
			drawInnerShadow(ctx, center, radius, ringWidth) {
				const shadowColor = 'rgba(255, 209, 191, 0.15)';
				const shadowWidth = 6;

				// 绘制外环阴影
				ctx.beginPath();
				ctx.arc(center, center- this.iconSize / 2, radius + ringWidth / 2 + shadowWidth / 2, 0, Math.PI * 2);
				ctx.setFillStyle(shadowColor);
				ctx.fill();

				// 绘制内环阴影(覆盖内部区域)
				ctx.beginPath();
				ctx.arc(center, center- this.iconSize / 2, radius - ringWidth / 2 - shadowWidth / 2, 0, Math.PI * 2);
				ctx.setFillStyle('#ffffff');
				ctx.fill();
			},
			onImageLoad() {
				// 图片加载成功
				this.imageLoaded = true;
				this.drawProgress();
			},
			// 模拟进度更新
			simulateCharging() {
				if (this.info.progress < 100) {
					setTimeout(() => {
						this.info.progress += 1;
						this.drawProgress();
						this.simulateCharging();
					}, 100);
				}
			}
		}
	}
</script>

<style scoped>
	.home {
		position: relative;
		width: 100vw;
		height: 100vh;
		background: #ecf0f1;
	}
	.home .chartBox {
		position: relative;
		left: 50%;
		top: 128rpx;
		transform: translateX(-50%);
		width: 480rpx;
		height: 480rpx;
		background: linear-gradient(180deg, #FFFFFF 0%, #fff8f6 100%);
		box-shadow: 0rpx 20rpx 20rpx 0rpx rgba(255, 209, 191, 0.1);
		border-radius: 100%;
	}

	.home .chartBox-progress {
		z-index: 2;
		position: relative;
		overflow: visible !important;
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
		height: 480rpx;
	}
	
	.home .content {
		z-index: 9;
		position: absolute;
		top: 144rpx;
		left: 50%;
		transform: translateX(-50%);
		display: flex;
		flex-direction: column;
		align-items: center;
	}
	
	.home .content-value {
		display: flex;
		align-items: baseline;
		flex-wrap: nowrap;
	}
	
	.home .content-value .value {
		font-weight: 500;
		font-size: 120rpx;
		color: #ff8859;
	}
	
	.home .content-value .text {
		margin-bottom: 8rpx;
		font-weight: 400;
		font-size: 32rpx;
		color: #ffded1;
	}
	.home .content-tips {
		margin: 0 0 20rpx;
	}
	
	.home .content-tips .text {
		font-weight: 400;
		font-size: 24rpx;
		color: #3F5680;
	}
	
	.home .content-tips .value {
		margin: 0 8rpx;
		font-weight: 600;
		font-size: 24rpx;
		color: #ff8859;
	}
	
</style>

二、最终效果:

相关推荐
说私域12 小时前
社群招募文案的核心构建要点与工具赋能路径——基于AI智能名片链动2+1模式商城小程序的实践研究
人工智能·小程序·私域运营
_ZeroKing14 小时前
自制智能门锁:NFC 刷卡 + 小程序远程开锁(完整实战记录)
嵌入式硬件·小程序·notepad++·arduino
郑州光合科技余经理14 小时前
可独立部署的Java同城O2O系统架构:技术落地
java·开发语言·前端·后端·小程序·系统架构·uni-app
雪芽蓝域zzs14 小时前
uniapp 取消滚动条
uni-app
阿斌_bingyu70916 小时前
眼镜店AR在线试戴小程序技术解决方案
小程序·ar
2401_8658548816 小时前
Uniapp和Flutter哪个更适合企业级开发?
flutter·uni-app
雪芽蓝域zzs16 小时前
uniapp 省市区三级联动
前端·javascript·uni-app
总爱写点小BUG16 小时前
UniApp 图标方案终极排坑:告别 FontClass,拥抱真 SVG 组件化
前端框架·uni-app
计算机毕设指导616 小时前
基于微信小程序的智能停车场管理系统【源码文末联系】
java·spring boot·微信小程序·小程序·tomcat·maven·intellij-idea
2501_9339072117 小时前
如何选择西安优质小程序开发服务与本凡码农合作?
科技·微信小程序·小程序