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

文章目录

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

需求

要绘制一个如此的进度条
[uni-app] canvas绘制圆环进度条_第1张图片

参考链接

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

官网api

基本问题的处理

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

1:画布旋转的问题

[uni-app] canvas绘制圆环进度条_第2张图片

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

[uni-app] canvas绘制圆环进度条_第3张图片

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

[uni-app] canvas绘制圆环进度条_第4张图片

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

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

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

[uni-app] canvas绘制圆环进度条_第5张图片

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

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

[uni-app] canvas绘制圆环进度条_第6张图片
绘制出的10等分小线段已经完成, 想要做到如下图效果. 我们只要在for循环内, 选出i=0,6,8即可
(即 100% 60% 80%进度)
[uni-app] canvas绘制圆环进度条_第7张图片
但是问题来了…
实际效果如下
[uni-app] canvas绘制圆环进度条_第8张图片
好像不对了…

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

我们为了要得到从0点位置那就是对画布进行逆时针的180°旋转
[uni-app] canvas绘制圆环进度条_第10张图片

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

于是得到了

[uni-app] canvas绘制圆环进度条_第11张图片
再进过处理

[uni-app] canvas绘制圆环进度条_第12张图片

再把圆环补全,去掉不用的小线段
[uni-app] canvas绘制圆环进度条_第13张图片
与效果图对比(UI图实际是有点错误的,但这不重要)
[uni-app] canvas绘制圆环进度条_第14张图片
最终效果:

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

[uni-app] canvas绘制圆环进度条_第15张图片

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

[uni-app] canvas绘制圆环进度条_第16张图片

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

[uni-app] canvas绘制圆环进度条_第17张图片

源码

<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>

你可能感兴趣的:(小程序,uni-app,vue,uni-app)