要绘制一个如此的进度条
uni-app使用canvas绘制时间刻度以及不显示问题处理
其实基本看参考链接学着搞, 不难的 . 主要是针对一些api的坑,要有了解就可以.
//0.1把画布先 逆时针旋转90度, 从0点绘制开始绘制
baseCtx.rotate(-90 * Math.PI / 180)
梳理一下流程, 如果要画一个圆环, 且要保证起始点是0点方向, 步骤是
1.画布逆时针旋转90度
2.画圆操作
3.恢复画布旋转角度(因为rotate()角度会叠加,为了防止计算混乱, 可以旋转画布)
(有人会问执行第三点
,不会把绘制的圆环又逆回去? 这里要明确的是, 画布是画布(即context), 绘制好的图像是绘制好的图像)
首先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>