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>

二、最终效果:

相关推荐
是江迪呀3 小时前
实时看大家都在干嘛?我靠一行监听函数,做了个轻互动小程序
前端·微信小程序
码视野3 小时前
课后报名小程序 — 从需求到原型的全栈实践
小程序
打瞌睡的朱尤7 小时前
微信小程序1~25
微信小程序·小程序
hnxaoli7 小时前
win10小程序(十八)剪切板循环粘贴
python·小程序
拖孩8 小时前
我用 AI 搓了一个"比谁更持久"的微信小游戏,AI实现只用了一天,微信审核却用了一个月!!!
微信小程序·ai编程·游戏开发
河北清兮网络科技19 小时前
短剧 APP 产品说明
小程序·uni-app·短剧
宠友信息1 天前
一套基于uniapp+springboot完整社区系统是如何实现的?友猫社区源码级功能解析
java·spring boot·后端·微服务·微信·uni-app
AI品信智慧数智人1 天前
文旅景区小程序集成数字人智能语音交互系统,山东品信解锁AI伴游新玩法✨
人工智能·小程序
医疗信息化王工1 天前
钉钉小程序开发实战:投诉管理系统
小程序·钉钉·开发·投诉管理
灵机一物1 天前
灵机一物AI原生电商小程序(已上线)-从“48 小时失联”到“长期可触达”:一套小程序公众号关注引导 + 订阅消息授权的产品化设计
小程序