文章目录
- 需求
- 参考链接
- 基本问题的处理
- 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: {// 计算中心点XcenterPointX() {return this.reactWH / 2;},// 计算中心点YcenterPointY() {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 - 180baseCtx.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>