项目需求
image.png项目需求分析
image.png以下是需求进行分析:
为了节省不必要的消耗,我们把灰色进去圆弧和有色进度圆弧以及小圆点,分两个canvas进行绘制
1:灰色圆弧进度绘制canvas
API默认起始点是从三点钟顺时针开始的,而需求起始点是从六点钟和九点钟等分点开始的,三点钟和等分点之间的夹角是135°,需要顺时针进行旋转135°的弧度值(135 * Math / 180),整个灰色圆弧的弧度长度为1.5 * Math.PI(项目需求分析的第一个图进行分析)
2:有色圆弧进度绘制canvas
a、API默认起始点是从三点钟顺时针开始的,而需求起始点是从六点钟和九点钟等分点开始的,三点钟和等分点之间的夹角是135°,需要顺时针进行旋转135°的弧度值(135 * Math / 180),画完有色圆弧进度后,需要在逆时针旋转135°的弧度值(135 * Math / 180),让起始点恢复到三点钟,为后续画小圆点做准备
b、小圆点的开始位置需要和有色圆弧进度开始位置相同,需要顺时针进行旋转135°的弧度值(135 * Math / 180),画完之后还是需要逆时针旋转135°的弧度值(135 * Math / 180),为了后续多次进行绘制
c、小圆点在每个象限的角度、坐标点的正负都不一样,我们根据API的起点在配合象限来进行判断和分析
象限 | 角度 | 坐标 | 以X轴计算有效角度 |
---|---|---|---|
第四象限 | [0°,90°] | (x, y) | angle |
第三象限 | (90°,180°] | (-x, y) | 180 - angle |
第二象限 | (180°,270°) | (-x, -y) | angle - 180 |
第一象限 | (270°,360°] | (x, -y) | 360 - angle |
sinA = 对边 / 斜边
cosA = 领边 / 斜边
弧度 = 角度 * Math.PI / 180
角度 = 弧度 / Math.PI * 180
两点之间的弧度值 = Math.atan2(y,x)
实现效果图
image.png
image.png
image.png
1.布局wxml
<view style="position: relative;width: 80vw;height: 80vw;border: 1rpx solid red;">
<canvas type="2d" id="proBg" style="width: 100%;height: 100%;"></canvas>
<canvas bindtouchstart="moveHandler" bindtouchmove="moveHandler" bindtouchend="moveEnd" type="2d" id="pro" style="position: absolute;left: 0;right: 0;top: 0;bottom: 0; width: 100%;height: 100%;"></canvas>
<view style="position: absolute;left: 50%;top: 50%;transform: translate(-50%,-50%);">{{twoRunTimeFormat}}</view>
</view>
<view style="display: flex;flex-direction: row;margin: 20rpx 0;">
<button bindtap="timeHandler" data-operation="{{1}}" type="default">加</button>
<button style="margin-left: 40rpx;" bindtap="timeHandler" data-operation="{{2}}" type="default">减</button>
</view>
<view style="display: flex;flex-direction: row;margin: 20rpx 0;">
<button bindtap="clickHandler" data-operation="{{1}}" type="default">开始</button>
<button style="margin-left: 40rpx;" bindtap="clickHandler" data-operation="{{2}}" type="default">暂停</button>
</view>
逻辑js
// pages/arc2/index.js
const app = getApp();
Page({
data: {
centerPointer: null, //canvas画布中心点
arcRadius: 0, //圆弧线的半径
arcWidth: 0, //圆弧线的宽度
pixelRatio: 0, //像素
ctx: null, //pro的ctx
validDis: 0, //有效范围的宽度
angleVlue: 0, //角度
radianValue: 0, //弧度
lastRadianValue: 0, //上次绘制的弧度值
secondRadianValue: 0, //每秒的弧度值
helfMinRadianValue: 0, //半分钟的弧度值
circleRadius: 0, //小圆点的半径
twoTime: 0, //设置时间
twoId: null, //循环标识
twoRunTimeFormat: '00:00', //格式化的时间字符串
isTwoTreat: false, //是否正在治疗
},
onLoad(options) {
this.initData();
},
initData() { //初始化一些配置参数&canvas
const width = app.globalData.width * 0.8;
const half = width / 2;
this.data.centerPointer = {
x: half,
y: half,
}
this.data.arcRadius = half * 0.8;
this.data.arcWidth = half * 0.05;
this.data.validDis = half * 0.2;
this.data.pixelRatio = app.globalData.pixelRatio;
this.data.secondRadianValue = 1.5 * Math.PI / (2 * 60);
this.data.helfMinRadianValue = 1.5 * Math.PI / (2 * 2);
// this.data.radianValue = 0 * 60 * this.data.secondRadianValue;
this.data.circleRadius = half * 0.065;
//画圆弧的灰色背景
const proBg = wx.createSelectorQuery();
proBg.select("#proBg")
.fields({
node: true,
size: true,
}).exec(res => {
const canvas = res[0].node;
const ctx = canvas.getContext('2d');
canvas.width = width * this.data.pixelRatio;
canvas.height = width * this.data.pixelRatio;
ctx.scale(this.data.pixelRatio, this.data.pixelRatio);
ctx.translate(this.data.centerPointer.x, this.data.centerPointer.y); //设置中心
//API默认是从三点钟开始,需求是从六点钟和九点钟的等分点开始的,需要顺时针旋转90°+45°=135°
ctx.rotate(135 * Math.PI / 180);
//画灰色背景圆弧
ctx.beginPath();
ctx.lineCap = 'round';
ctx.lineWidth = this.data.arcWidth;
ctx.strokeStyle = '#BEBEBE';
ctx.arc(0, 0, this.data.arcRadius, 0, 1.5 * Math.PI);
ctx.stroke();
});
//初始化有色圆弧进度
const pro = wx.createSelectorQuery();
pro.select("#pro")
.fields({
node: true,
size: true,
})
.exec(res => {
const canvas = res[0].node;
const ctx = canvas.getContext('2d');
canvas.width = width * this.data.pixelRatio;
canvas.height = width * this.data.pixelRatio;
ctx.scale(this.data.pixelRatio, this.data.pixelRatio);
ctx.translate(this.data.centerPointer.x, this.data.centerPointer.y); //设置中心点
this.data.ctx = ctx;
this.droPro();
});
},
moveHandler(e) { //触摸点击或者滑动监听事件
//获取触摸点
const point = {
x: e.touches[0].x,
y: e.touches[0].y
};
//计算触摸点到中心点的距离
const distance = Math.sqrt(Math.pow(this.data.centerPointer.x - point.x, 2) + Math.pow(this.data.centerPointer.y - point.y, 2));
//触摸范围最大有效距离
const maxDistance = this.data.arcRadius + this.data.validDis;
//触摸范围最小有效距离
const minDistance = this.data.arcRadius - this.data.validDis;
// console.log(maxDistance,distance,minDistance);
//判断触摸距离是否在最大和最小距离之内
if (distance >= minDistance && distance <= maxDistance) {
//计算弧度值 初始点顺时针旋转了135°,所以这里要减135 * Math.PI / 180的弧度值
const radian = Math.atan2(point.y - this.data.centerPointer.y, point.x - this.data.centerPointer.x) - 135 * Math.PI / 180;
// console.log(radian);
if (radian < 0 && radian >= -0.785) { //特殊处理 在六点钟与六点钟和九点钟等分点范围内(90°~135°) 设置为最小弧度值0
this.data.radianValue = 0;
return;
} else if (radian < -0.785 && radian > -1.57) { //特殊处理 在六点钟与五点钟和六点钟等分点范围内(45°~90°) 设置为最大弧度值1.5*Math.PI
this.data.radianValue = this.data.helfMinRadianValue * 4;
return;
} else {
this.data.radianValue = radian;
}
//弧度值转化为角度
this.data.angleVlue = radian / Math.PI * 180;
// console.log('radian', radian);
this.droPro();
}
},
moveEnd(e) { //触摸结束
let radianValue = this.data.radianValue;
if (radianValue === this.data.lastRadianValue) return; //如果与上次的弧度值相同,则进行拦截,减少绘制,性能消耗
const helfMinRadianValue = this.data.helfMinRadianValue;
//根据触摸弧度值范围来判断在那个范围,从而得出最后的弧度值和时间
let timeValue = 0; //时间--秒
if ((radianValue > 0 && radianValue < 0.78) || (radianValue < 0 && radianValue <= -5.1)) { // 0.5 * 60秒范围
radianValue = helfMinRadianValue;
timeValue = 0.5 * 60;
} else if (radianValue > -5.1 && radianValue <= -3.9) { // 1 * 60秒范围
radianValue = helfMinRadianValue * 2;
timeValue = 1 * 60;
} else if (radianValue > -3.9 && radianValue <= -2.7) { // 1.5 * 60秒范围
radianValue = helfMinRadianValue * 3;
timeValue = 1.5 * 60;
} else if ((radianValue > -2.7 && radianValue <= -1.5) ||
(radianValue == helfMinRadianValue * 4)) { // 2 * 60秒范围
radianValue = helfMinRadianValue * 4;
timeValue = 2 * 60;
}
this.data.radianValue = radianValue;
this.data.angleVlue = radianValue / Math.PI * 180; //弧度值转化为角度
this.data.lastRadianValue = radianValue;
const min = Math.floor(timeValue / 60);
const second = timeValue % 60;
this.setData({
twoTime: timeValue,
twoRunTimeFormat: (min < 10 ? '0' + min : min) + ':' + (second < 10 ? '0' + second : second),
})
this.droPro();
},
droPro() { //绘制有色进度和小圆点
if (this.data.ctx == null) return;
//清空上次一的ctx画图
this.data.ctx.clearRect(-this.data.centerPointer.x, -this.data.centerPointer.y,
this.data.centerPointer.x * 2, this.data.centerPointer.y * 2);
//圆的初始点在三点钟方向,需求是六点和九点之间等分点开始,需要顺时针旋转90°+45°=135°
this.data.ctx.rotate(135 * Math.PI / 180);
//画有色进度圆弧
this.data.ctx.beginPath();
this.data.ctx.lineCap = 'round';
this.data.ctx.lineWidth = this.data.arcWidth;
this.data.ctx.strokeStyle = 'green';
this.data.ctx.arc(0, 0, this.data.arcRadius, 0, this.data.radianValue);
this.data.ctx.stroke();
//前面顺时针旋转了135°,要把起始点恢复到3点钟开始
this.data.ctx.rotate(-135 * Math.PI / 180);
let angleVlue = this.data.angleVlue;
const arcRadius = this.data.arcRadius;
let point = {
x: 0,
y: 0
}; //小圆点的坐标
// console.log(this.data.radianValue, angleVlue)
//是对照X轴进行计算 --- 通过角度来判断是那个一象限,得到真正的小圆点的坐标
if (angleVlue >= 0 && angleVlue <= 90) { //第四象限
point = {
x: Math.cos(angleVlue * Math.PI / 180) * arcRadius,
y: Math.sin(angleVlue * Math.PI / 180) * arcRadius
}
} else if (angleVlue > 90 && angleVlue <= 180) { //第三象限
point = {
x: -Math.cos((180 - angleVlue) * Math.PI / 180) * arcRadius,
y: Math.sin((180 - angleVlue) * Math.PI / 180) * arcRadius
}
} else if (angleVlue > 180 && angleVlue <= 270) { //第二象限
point = {
x: -Math.cos((angleVlue - 180) * Math.PI / 180) * arcRadius,
y: -Math.sin((angleVlue - 180) * Math.PI / 180) * arcRadius
}
} else if (angleVlue > 270 && angleVlue <= 360) { //第一象限
point = {
x: Math.cos((360 - angleVlue) * Math.PI / 180) * arcRadius,
y: -Math.sin((360 - angleVlue) * Math.PI / 180) * arcRadius
}
} else if (angleVlue < 0) { //in 第二现象 & 第一象限 & 第四象限
point = {
x: -Math.cos((angleVlue - 180) * Math.PI / 180) * arcRadius,
y: -Math.sin((angleVlue - 180) * Math.PI / 180) * arcRadius
}
}
//目前小圆点是从3点钟为起始点,为了和有色进度圆弧保持一致,需要顺时针旋转135°,起始点保持一致
this.data.ctx.rotate(135 * Math.PI / 180);
//画小圆点
this.data.ctx.beginPath();
this.data.ctx.fillStyle = 'red';
this.data.ctx.arc(point.x, point.y, this.data.circleRadius, 0, 2 * Math.PI);
this.data.ctx.fill();
//前面顺时针旋转了135°,为了后续绘制,需要逆时针旋转135°,把起始原点恢复到3点钟开始
this.data.ctx.rotate(-135 * Math.PI / 180);
},
timeHandler(e) { //加减时间监听事件
let twoTime = this.data.twoTime;
const operation = e.currentTarget.dataset.operation;
switch (operation) {
case 1: //加
if (twoTime >= 2 * 60) {
wx.showToast({
title: '最大时间啦',
mask: false,
icon: 'none',
})
return;
}
twoTime += 30;
const min = Math.floor(twoTime / 60);
const second = twoTime % 60;
const twoRunTimeFormat = (min < 10 ? '0' + min : min) + ':' + (second < 10 ? '0' + second : second);
this.setData({
twoTime: twoTime,
twoRunTimeFormat: twoRunTimeFormat,
});
break;
case 2: //减
if (twoTime <= 0) {
wx.showToast({
title: '最小时间啦',
mask: false,
icon: 'none'
})
return;
}
twoTime -= 30;
const _min = Math.floor(twoTime / 60);
const _second = twoTime % 60;
const _twoRunTimeFormat = (_min < 10 ? '0' + _min : _min) + ':' + (_second < 10 ? '0' + _second : _second);
this.setData({
twoTime: twoTime,
twoRunTimeFormat: _twoRunTimeFormat,
})
break;
}
this.data.radianValue = twoTime * this.data.secondRadianValue;
this.data.angleVlue = this.data.radianValue / Math.PI * 180;
this.droPro();
},
clickHandler(e) { //点击事件
const operation = e.currentTarget.dataset.operation;
switch (operation) {
case 1:
let twoTime = this.data.twoTime;
if (twoTime == 0) return;
if (this.data.isTwoTreat) {
wx.showToast({
title: '正在倒计时',
mask: false,
icon: 'none'
})
return;
}
this.setData({
isTwoTreat: true,
})
this.data.twoId = setInterval(() => {
if (twoTime <= 0) {
this.setData({
twoTime: 0,
isTwoTreat: false,
})
clearInterval(this.data.twoId);
return;
}
twoTime--;
console.log(twoTime);
this.data.radianValue = twoTime * this.data.secondRadianValue;
this.data.angleVlue = this.data.radianValue / Math.PI * 180;
const min = Math.floor(twoTime / 60);
const second = twoTime % 60;
const twoRunTimeFormat = (min < 10 ? '0' + min : min) + ':' + (second < 10 ? '0' + second : second);
this.droPro();
this.setData({
twoRunTimeFormat: twoRunTimeFormat,
});
}, 1000);
break;
case 2:
clearInterval(this.data.twoId);
this.setData({
isTwoTreat: false,
twoTime: 0,
twoRunTimeFormat: '00:00',
radianValue: 0,
angleVlue: 0,
})
this.droPro();
break;
}
},
})
网友评论