效果图:
思路:过程分为三步,第1步,先画虚线底部背景,第2步,画动态的虚线(已选虚线蓝颜色),第3步,画动态的外标(已选虚线外位置的标),相关联的有1和2、2和3,1和2比较明显,颜色背景位置相同,2覆盖在1上,2和3终点位置相同,也就是计算弧度是一样的。
代码实现:
<view class="progress_item">
<canvas class="progress_ring" type="2d" id="bgline"></canvas>
<canvas class="progress_draw" type="2d" id="drawLine"></canvas>
<canvas class="progress_move" type="2d" id="drawMove" bind:touchstart="moveDraw" bind:touchmove="moveDraw"></canvas>
</view>
css:
.progress_item{width:100%;position: relative;}
.progress_ring{width:280px;height: 140px;margin: 30px auto;}
.progress_draw{position: absolute;left:50%;transform: translateX(-50%);z-index: 3;top:0;width:280px;height: 140px;}
.progress_move{position: absolute;left:50%;transform: translateX(-50%);z-index: 5;top:-12px;width:300px;height: 170px;}
js:
let bgLineCtx,drawCtx,markCtx;
let markTimeStamp = 0;
let gbProgress = 0;
// 初始化画布
initRing(){
const query = wx.createSelectorQuery()
query.selectAll('#bgline,#drawLine,#drawMove').fields({ node: true, size: true }).exec((res) => {
const dpr = wx.getSystemInfoSync().pixelRatio
// 1、背景 半圆背景虚线底
const bgLineCanvas = res[0][0].node
bgLineCtx = bgLineCanvas.getContext('2d');
bgLineCanvas.width = res[0][0].width * dpr
bgLineCanvas.height = res[0][0].height * dpr
bgLineCtx.scale(dpr, dpr);
bgLineCtx.clearRect(0, 0, 280, 140);
bgLineCtx.beginPath()
bgLineCtx.strokeStyle = "#aaa";
bgLineCtx.lineWidth = 12;
bgLineCtx.lineCap = "line";
bgLineCtx.setLineDash([2, 12]);
bgLineCtx.arc(140,140,130,Math.PI,2*Math.PI);
bgLineCtx.stroke();
// 2、选中半圆虚线
const drawLineCanvas = res[0][1].node
drawCtx = drawLineCanvas.getContext('2d');
drawLineCanvas.width = res[0][1].width * dpr
drawLineCanvas.height = res[0][1].height * dpr
drawCtx.scale(dpr, dpr);
drawCtx.strokeStyle = "#2a82e4";
drawCtx.lineWidth = 12;
drawCtx.lineCap = "line";
drawCtx.setLineDash([2, 12]);
this.drawPress(1);
// 3、手指滑动
const drawMoveCanvas = res[0][2].node
markCtx = drawMoveCanvas.getContext('2d');
drawMoveCanvas.width = res[0][2].width * dpr
drawMoveCanvas.height = res[0][2].height * dpr
markCtx.scale(dpr, dpr);
markCtx.translate(150, 150);
markCtx.strokeStyle = "#2a82e4";
markCtx.lineWidth = 4;
markCtx.lineCap = 'round';
this.drawRingDot(Math.PI);
})
},
// 虚线占比 num为1时有动画效果
drawPress(num){
let addNum = gbProgress / 20; // 转到多少 π(分为100份)每次转多少 π
function draw(x){
drawCtx.clearRect(0,0,280,140);
drawCtx.beginPath()
drawCtx.arc(140,140,130,Math.PI,Math.PI+x);
drawCtx.stroke();
}
function animate(s){
if(num == 1){
setTimeout(function(){
s += addNum;
if (s >= gbProgress) {
draw(gbProgress);
}else {
draw(s);
animate(s);
}
}, 20);
}else{
draw(gbProgress);
}
}
animate(0);
},
// 虚线外的指标
drawRingDot(angle){
markCtx.clearRect(-150, -150, 300, 170);
markCtx.beginPath()
markCtx.moveTo((148)*Math.cos(angle),(148)*Math.sin(angle));
markCtx.lineTo((140)*Math.cos(angle),(140)*Math.sin(angle));
markCtx.stroke();
},
// 手指触摸
moveDraw(e){
let x = e.changedTouches[0].x;
let y = e.changedTouches[0].y;
if(e.type == "touchstart"){
markTimeStamp = parseInt(e.timeStamp);
}
const radius = 150; // 半圆环的半径
let maxDistance = radius+10; // 最大圆的距离
let minDistance = radius-30; // 最小圆的距离
let distance = Math.sqrt(Math.pow(x-radius, 2) + Math.pow(y-radius, 2));
let num = parseInt(e.timeStamp - markTimeStamp);
if(e.type=="touchmove" && num >= 200){
markTimeStamp = parseInt(e.timeStamp);
}
if(y >= radius){
y = radius
}
if (distance <= maxDistance && distance >= minDistance) {
let radian = Math.atan2(y-radius, x-radius);
let realAngle = (radian/ Math.PI+2).toFixed(2)*Math.PI; //弧度转化为角度
let pAg = ((radian/ Math.PI+1)*100).toFixed(2);
// console.log('val',radian,realAngle,pAg)
if(pAg == 200){pAg = 0;}
if(pAg >= 0 && pAg <= 100){
gbProgress = pAg * (Math.PI/100);
this.setData({
progress: parseInt(pAg)
})
this.drawPress(0);
}
this.drawRingDot(realAngle);
}
},