[uni-app] canvas绘制圆环进度条

news2025/7/13 4:07:06

文章目录

    • 需求
    • 参考链接
    • 基本问题的处理
      • 1:画布旋转的问题
      • 2:注意arc()的起始位置是3点钟方向
      • 3: 如果绘制1.9*Matn.PI的圆环, 要保证其实位置在0点方向?
      • 4:小线段怎么画, 角度怎么处理?
    • 源码

需求

要绘制一个如此的进度条
在这里插入图片描述

参考链接

uni-app使用canvas绘制时间刻度以及不显示问题处理

官网api

基本问题的处理

其实基本看参考链接学着搞, 不难的 . 主要是针对一些api的坑,要有了解就可以.

1:画布旋转的问题

在这里插入图片描述

2:注意arc()的起始位置是3点钟方向

在这里插入图片描述

3: 如果绘制1.9*Matn.PI的圆环, 要保证其实位置在0点方向?

在这里插入图片描述

			//0.1把画布先 逆时针旋转90度, 从0点绘制开始绘制
				baseCtx.rotate(-90 * Math.PI / 180)

梳理一下流程, 如果要画一个圆环, 且要保证起始点是0点方向, 步骤是
1.画布逆时针旋转90度
2.画圆操作
3.恢复画布旋转角度(因为rotate()角度会叠加,为了防止计算混乱, 可以旋转画布)

(有人会问执行第三点,不会把绘制的圆环又逆回去? 这里要明确的是, 画布是画布(即context), 绘制好的图像是绘制好的图像)

在这里插入图片描述

4:小线段怎么画, 角度怎么处理?

首先360°的圆,分成10份, 角度单位是36°
那么处理办法也是一样的
for循环内
1.画布旋转36° / 72° / 108° …
2.绘制小线段
3.画布恢复 36° / 72° / 108° …

在这里插入图片描述
绘制出的10等分小线段已经完成, 想要做到如下图效果. 我们只要在for循环内, 选出i=0,6,8即可
(即 100% 60% 80%进度)
在这里插入图片描述
但是问题来了…
实际效果如下
在这里插入图片描述
好像不对了…

通过控制不同下标的小线段的绘制, 得到如下的分析图,
(小线段因为是基于moveTo/lineTo,绘制的)
在这里插入图片描述
当i越大,小线段的起始点与结束点的距离也越大, 所以i=0的时候, 小线段最短,
在这里插入图片描述
那么我们就发现, 他是从原始画布的90°方向开始绘制的,

我们为了要得到从0点位置那就是对画布进行逆时针的180°旋转
在这里插入图片描述

那么步骤就是
for循环内
1.画布旋转36°-180° / 72°-180° / 108°-180° …
2.绘制小线段
3.画布恢复 逆向的 36°-180° / 72°-180° / 108°-180° …

于是得到了

在这里插入图片描述
再进过处理

在这里插入图片描述

再把圆环补全,去掉不用的小线段
在这里插入图片描述
与效果图对比(UI图实际是有点错误的,但这不重要)
在这里插入图片描述
最终效果:

	<scoreLevelCanvas :score="277" :progress="0.4"></scoreLevelCanvas>

在这里插入图片描述

<scoreLevelCanvas :score="277" :progress="0.68"></scoreLevelCanvas>

在这里插入图片描述

<scoreLevelCanvas :score="277" :progress="0.97"></scoreLevelCanvas>

在这里插入图片描述

源码

<template>
	<view class="score-level-box" :style="{'width':reactWH+'px','height':reactWH+'px'}">
		<!-- baseCanvas -->
		<canvas id="scoreLevelBase" canvas-id="scoreLevelBase"
			:style="{'width':reactWH+'px','height':reactWH+'px'}"></canvas>
		<!-- progressCanvas -->
		<canvas id="scoreLevelProgress" canvas-id="scoreLevelProgress"
			style="position: absolute;left: 0;top:0"
			:style="{'width':reactWH+'px','height':reactWH+'px'}"></canvas>
		<view class="score-view" :style="{'fontSize':scoreFontSize+'px','color':strokeColor}">
			{{scoreString}}
		</view>
	</view>
</template>

<script>
	export default {
		name: "scoreLevelCanvas",
		props: {
			// canvas视图的宽高(矩形->正方形)
			reactWH: {
				type: Number,
				default: () => 200,
			},
			// 进度
			progress: {
				type: Number,
				default: () => 0.4
			},
			score: {
				type: Number,
				default: () => 20,
			}
		},
		data() {
			return {
				baseCtx: null,
				progressCtx: null,
			};
		},
		computed: {
			// 计算中心点X
			centerPointX() {
				return this.reactWH / 2;
			},
			// 计算中心点Y
			centerPointY() {
				return this.reactWH / 2;
			},
			//计算半径
			radius() {
				return this.reactWH / 2 * 0.9;
			},
			//计算小线段绘制的起始 - 偏内0.1个半径
			dotLineMoveTo() {
				return this.reactWH / 2 * (0.9 - 0.1);
			},
			//计算小线段绘制的结束 - 偏外0.1个半径
			dotLineLineTo() {
				return this.reactWH / 2 * (0.9 + 0.1);
			},
			//计算进度环的厚度
			progressWidth() {
				// 进度环的厚度, 设置为半径的8%
				return (this.reactWH / 2) * 0.08;
			},
			//计算小线段的厚度
			dotLineWidth() {
				// 小线段的厚度, 同圆环厚度
				return (this.reactWH / 2) * 0.08;
			},
			//计算进度环颜色
			strokeColor() {
				let strokeColor = ""
				if (this.progress < 0.6) {
					strokeColor = "#EA532F"
				} else if (this.progress >= 0.6 && this.progress < 0.8) {
					strokeColor = "#F9B93C"
				} else if (this.progress >= 0.8) {
					strokeColor = "#4CBA85"
				}
				return strokeColor
			},
			//计算得分字段
			scoreString() {
				return (this.score || "") + "分"
			},
			//计算得分字体大小
			scoreFontSize() {
				return this.radius * 0.4
			}
		},
		mounted() {
			// 绘制 基础圆样式
			this.drawScoreLevelBaseView()
			// 绘制 动态进度圆
			this.drawScoreLevelProgressView()
			// 最终绘制 - draw()
			this.draw()
		},
		methods: {
			// 绘制 基础圆样式
			drawScoreLevelBaseView() {
				const baseCtx = uni.createCanvasContext("scoreLevelBase", this);
				baseCtx.save();
				// 把圆心移到矩形中心点
				baseCtx.translate(this.centerPointX, this.centerPointY);

				//0.1把画布先 逆时针旋转90度, 从0点绘制开始绘制
				baseCtx.rotate(-90 * Math.PI / 180)

				//0.2绘制圆心, 方便观察
				// baseCtx.beginPath()
				// baseCtx.setStrokeStyle('#090')
				// baseCtx.arc(0, 0, 3, 0, 2 * Math.PI)
				// baseCtx.stroke()

				//1.绘制基础圆
				baseCtx.beginPath()
				baseCtx.setStrokeStyle("#EAEAEA")
				baseCtx.setLineWidth(this.progressWidth)
				baseCtx.setLineCap('round')
				baseCtx.arc(0, 0, this.radius, 0, 2 * Math.PI)
				baseCtx.stroke()
				//1.1恢复旋转角度(目的是恢复画布)
				baseCtx.rotate(90 * Math.PI / 180)

				//2. 绘制小线段 (360°/10)
				for (var i = 0; i < 10; i++) {
					// 计算每个小线段的旋转角度- 顺时针旋转画布
					// 发现,小线段在原始画布下, 是从90°方向顺时针绘制的, 因此要逆时针旋转180°
					let rotateDeg = i * 36 - 180
					baseCtx.rotate(rotateDeg * Math.PI / 180)
					baseCtx.beginPath()
					baseCtx.setLineWidth(0.3) // 预设一个极细的厚度,
					baseCtx.setLineCap('round')
					baseCtx.setStrokeStyle('#EAEAEA')
					// baseCtx.moveTo(0, this.dotLineMoveTo - (i * 8)) // (i*8)为了测试方便,
					baseCtx.moveTo(0, this.dotLineMoveTo)
					baseCtx.lineTo(0, this.dotLineLineTo)
					// 保留 100% 60% 80%进度的小线段
					if (i == 0 || i == 6 || i == 8) {
						baseCtx.setLineWidth(this.dotLineWidth)
					}
					baseCtx.stroke()
					// 绘制完成小线段后, 恢复画布旋转角度;
					baseCtx.rotate(-rotateDeg * Math.PI / 180)
				}
				this.baseCtx = baseCtx;
			},
			// 绘制 进度圆
			drawScoreLevelProgressView() {
				const progressCtx = uni.createCanvasContext("scoreLevelProgress", this);
				progressCtx.save();
				// 把圆心移到矩形中心点
				progressCtx.translate(this.centerPointX, this.centerPointY);

				//0.1把画布先 逆时针旋转90度, 从0点绘制开始绘制
				progressCtx.rotate(-90 * Math.PI / 180)

				//0.2绘制圆心, 方便观察
				// progressCtx.beginPath()
				// progressCtx.setStrokeStyle('#113')
				// progressCtx.arc(0, 0, 3, 0, 2 * Math.PI)
				// progressCtx.stroke()

				//1.绘制基础圆


				progressCtx.beginPath()
				progressCtx.setStrokeStyle(this.strokeColor)
				progressCtx.setLineWidth(this.progressWidth)
				progressCtx.setLineCap('round')
				progressCtx.arc(0, 0, this.radius, 0, 2 * this.progress * Math.PI)
				progressCtx.stroke()
				//1.1恢复旋转角度(目的是恢复画布)
				progressCtx.rotate(90 * Math.PI / 180)
				this.progressCtx = progressCtx;
			},
			draw() {
				setTimeout(() => {
					this.baseCtx.draw();
					this.progressCtx.draw();
				}, 50)
			},
		}
	}
</script>

<style lang="scss">
	.score-level-box {
		position: relative;
		// background-color: #91f;

		.score-view {
			position: absolute;
			top: 50%;
			left: 50%;
			transform: translate(-50%, -50%);
			font-family: PingFangSC-Medium, PingFang SC;
			font-weight: 500;

		}
	}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1103189.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

线段树【java实现】

一、解决问题 区间最值和区间求和问题 力扣相关题目&#xff1a; ​​​​​​303. 区域和检索 - 数组不可变 729. 我的日程安排表 I 二、线段树定义 平衡二叉树&#xff0c;数组中的元素都存储在叶子结点中&#xff0c;如图是一个求区间最大值的线段树。 已知数组arr[11…

电源特性测试之电源模块负载调整率测试方法及测试条件

负载调整率是衡量电源好坏的重要指标&#xff0c;它反映的是当负载电流变化时&#xff0c;稳压电源输出电压相应的变化情况。好的电源负载变化时引起的输出变化较小&#xff0c;通常是在3%-5%。负载调整率是电源模块测试的一个重要步骤&#xff0c;今天纳米软件将为大家介绍负载…

Yakit工具篇:综合目录扫描与爆破的使用

简介&#xff08;来自官方文档&#xff09; 目录扫描是一种常用的Web应用程序安全测试技术&#xff0c;用于发现Web应用程序中存在的可能存在的漏洞和弱点。其原理是通过对Web应用程序中的目录和文件进行遍历&#xff0c;来发现可能存在的安全漏洞和风险。 具体来说&#xff…

大语言模型在推荐系统的实践应用

本文从应用视角出发&#xff0c;尝试把大语言模型中的一些长处放在推荐系统中。 01 背景和问题 传统的推荐模型网络参数效果较小(不包括embedding参数)&#xff0c;训练和推理的时间、空间开销较小&#xff0c;也能充分利用用户-物品的协同信号。但是它的缺陷是只能利用数据…

《进化优化》第5章 进化规划

文章目录 5.1 连续进化规划5.2 有限状态机优化5.3 离散进化规划5.4 囚徒困境5.5 人工蚂蚁问题 5.1 连续进化规划 目的&#xff1a;最小化f(x)&#xff0c; 这里的x是一个n维向量&#xff0c;假定对所有的x, f(x)>0。 进化规划从随机生成的一个个体种群{xi}开始, 按如下方式…

Umi3实战教程

一、框架介绍 umi是蚂蚁金服的前端开发框架&#xff0c;它内置了路由、web/移动端UI库、数据流、权限控制、常用hooks库、构建、部署、测试、等等一些工具&#xff0c;几乎涵盖了正常前端开发要用到的所有工具。 二、环境准备 pnpm 相比npm、yarn&#xff0c;pnpm更小更快扁平…

为大模型而生!顶流大佬发起成立学术会议 COLM,或成为未来 NLP 最强顶会?!

夕小瑶科技说 原创 作者 | 智商掉了一地、ZenMoore 前段时间&#xff0c;ACL 2024 的主席公开抨击称“ arXiv是科研的毒瘤”&#xff0c;这引发了大范围的争论。 一时间&#xff0c;大家对 *CL 的抵触情绪愈发高涨&#xff0c;绝大多数学界都在这场辩论中站在了支持 arXivTwit…

PreparedStatement

使用参数化查询&#xff1a;使用预编译的语句和参数化查询来执行SQL语句&#xff0c;而不是将用户输入直接嵌入到SQL语句中。这将帮助防止恶意输入注入SQL语句。

Zoho WorkDrive荣获专业研究机构评定的“Leader”称号

近年&#xff0c;在云计算、大数据、移动互联网、社交所引领的数字化转型变革中&#xff0c;企业对于数字资产的保护和利用愈加重视。相较于结构化数据&#xff0c;企业对于非结构化数据&#xff08;文档、图片、音视频等&#xff09;管理的需求更强、难度更大。 同时&#xf…

NodeJS 菜鸟教程目录

NodeJS 七天入门教程 谁适合阅读本教程? 前端开发者和希望构建后端的开发者:如果你是一名前端开发者,或者是一名希望构建后端的开发者,那么本教程将为你提供一个很好的学习Node.js的机会。通过学习本教程,你可以更好地了解后端开发的技术和Node.js在后端开发中的应用。初学…

日常学习记录随笔-redis实战

redis的持久化&#xff08;rdb,aof,混合持久化&#xff09; redis的主从架构以及redis的哨兵架构 redis的clusterredis 是要做持久化的&#xff0c;一般用redis会把数据放到缓存中为了提升系统的性能 如果redis没有持久化&#xff0c;重启的化数据就会丢失&#xff0c;所有的请…

【LeetCode热题100】--31.下一个排列

31.下一个排列 思路&#xff1a; 方法&#xff1a;两遍扫描 注意到下一个排列总是比当前排列要大&#xff0c;除非该排列已经是最大的排列。我们希望找到一种方法&#xff0c;能够找到一个大于当前序列的新序列&#xff0c;且变大的幅度尽可能小。具体地&#xff1a; 我们需要…

5年经验之谈 —— App测试、Web测试和接口测试一般测试流程!

app测试流程&#xff1a; 1、需求分析&#xff0c;了解具体需求 2、测试准备&#xff1a;原型图、效果图、需求文件、测试用例、用例评审、各种测试数据准备 3、测试环节&#xff1a;接受版本&#xff0c;开始执行 1&#xff09;冒烟测试&#xff1a;对版本质量的控制以及此…

【LeetCode: 260. 只出现一次的数字 III | 位运算 | 哈希表】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

ModStartCMS v7.4.0 公共图片库支持,安全功能升级

ModStart 是一个基于 Laravel 模块化极速开发框架。模块市场拥有丰富的功能应用&#xff0c;支持后台一键快速安装&#xff0c;让开发者能快的实现业务功能开发。 系统完全开源&#xff0c;基于 Apache 2.0 开源协议&#xff0c;免费且不限制商业使用。 功能特性 丰富的模块市…

C# 开发工具包 – 现已正式发布

作者&#xff1a;Wendy Breiding 排版&#xff1a;Alan Wang 今天&#xff0c;我们很高兴地宣布 C# 开发工具包正式发布&#xff0c;C# 开发工具包是一个 Visual Studio Code 扩展&#xff0c;为 Linux、macOS 和 Windows 带来了改进的编辑器优先 C# 开发体验。 谢谢社区的努…

文件传输软件的挑战与发展趋势

无论是在教育、医疗、金融、媒体、政府等行业&#xff0c;还是在个人生活和工作中&#xff0c;文件传输软件都有着广泛的应用价值和意义。然而&#xff0c;随着信息技术的发展和数据量的增长&#xff0c;文件传输软件也面临着一些挑战和问题&#xff0c;同时也有着一些发展趋势…

eNSP在hybrid接口上配置vlan

一、什么是vlan VLAN&#xff08;Virtual Local Area Network&#xff0c;虚拟局域网&#xff09;是一种通信技术&#xff0c;它可以将一个物理的局域网在逻辑上划分成多个广播域。每个VLAN都是一个广播域&#xff0c;VLAN内的主机可以直接通信&#xff0c;而VLAN之间则不能直…

SAP-QM-质检收货时报错

采购订单收货时&#xff0c;报错,点击蓝字查看未清的检验批&#xff0c;做使用决策QA11&#xff0c;完成后回复正常。

论文阅读-FCD-Net: 学习检测多类型同源深度伪造人脸图像

一、论文信息 论文题目&#xff1a;FCD-Net: Learning to Detect Multiple Types of Homologous Deepfake Face Images 作者团队&#xff1a;Ruidong Han , Xiaofeng Wang , Ningning Bai, Qin Wang, Zinian Liu, and Jianru Xue &#xff08;西安理工大学&#xff0c;西安交…