最近要实现一个圆环拖动实现温度控制的需求,综合网上搜寻的各类文章,结合自己的理解,写下自己的思路
获取画布元素
使用小程序的画布组件并获取canvas实例,此处参考小程序开发文档
<canvas
type="2d"
id="myCanvas"
disable-scroll
bindtouchstart="touch"
bindtouchmove="move">
</canvas>
<style>
#myCanvas{
background: purple;
width:300px;
height:300px;
margin: 50px auto;
}
</style>
const query = wx.createSelectorQuery()
query.select('#myCanvas')
.fields({ node: true, size: true })
.exec((res) => {
const canvas = res[0].node
const ctx = canvas.getContext('2d')
const dpr = wx.getSystemInfoSync().pixelRatio
canvas.width = res[0].width * dpr
canvas.height = res[0].height * dpr;
const { width, height } = res[0];
ctx.scale(dpr, dpr)
this.setData({ ctx });
})
绘制圆环
圆环进度显示主要实现方式是两个圆的重叠,即下层圆为底图,上层圆为显示的内容比值
绘制底图
不改变画布中心点,其中心点为(0,0);圆在画布中心,圆的中心点为(150,150)
this.data.ctx.clearRect(0,0,300,300); // 清空画布内容
this.data.ctx.beginPath();
this.data.ctx.strokeStyle = "rgba(255,255,255,0.4)";
this.data.ctx.lineWidth = 20;
this.data.ctx.arc(150,150,130,0.75*Math.PI,0.25*Math.PI); //半圆
this.data.ctx.stroke();
绘制遮罩层
主要思路:主要是控制下图这个值,圆环的开始位置都是0.75不改变,改变的是结束点的位置
this.data.ctx.beginPath()
this.data.ctx.arc(150, 150, 120, 0.75 * Math.PI, path * Math.PI) // hd是变量
this.data.ctx.lineWidth="20"
this.data.ctx.strokeStyle="orange"
this.data.ctx.stroke()
思路理解
输入数字绘制遮罩层
这个比较简单,就是存粹数学中的比例取值就可以了,但需要注意的是,当输入的值大于总值的时候,遮罩层的形状不能超出底层的范围
let number = 5; // 需要设定的值
let total = 30; // 总步数
path = 1.5 * number / total + 0.75;
鼠标拖动绘制遮罩层
这里需要用到atan2
(这是我的理解,如有不对,欢迎指正)
atan2
的原点为(0,0),点A到x轴角度是atan2(y = 10, x = 10)
当求中心点(150,150)到点A的距离是 atan2(10-150,10-150)
计算path
的值需要的是顺时针方向的度数数值(0,0.5,1,1.5,2)上述数值,atan2
得到的是弧度值(我是理解为度数)
主要分为两种情况:
- 当为正数时
转换为path值需要 / Math.PI
,即 B = atan2(y-150,x-150) / Math.PI
- 当为负数时
// 计算剩余的夹角值
let rest = Math.PI - (atan2(y-150,x-150)) / Math.PI;
// 再加上x轴下方
let result = rest + 1;
- 当path的在0.25-0.75区间 不绘制任何形状
绘制跟随点
ctx.beginPath();
let x = -Math.cos(path * Math.PI) * 120; // 计算x轴距离 120是半径 参考三角函数
let y = -Math.sin(path * Math.PI) * 120; // 计算y轴距离 120是半径 参考三角函数
ctx.arc(150-x, 150-y, 5, 0, 2 * Math.PI); // 150是圆中心坐标
ctx.fillStyle = 'green';
ctx.fill();
// path * Math.PI 转换为度数
全部代码
Page({
data: {
number:5,
total: 30,
ctx: null,
},
// 画布触摸移动事件
move(e) {
const {x, y} = e.touches[0];
let degree;
let r = Math.atan2(y-150, x-150);
console.log(r);
if (r < 0 ) {
degree = (Math.PI + r) / Math.PI + 1;
} else {
degree = r / Math.PI;
}
// 判断圆弧可移动的范围(没有圆弧的部分不移动)
if (degree > 0.75 || degree < 0.25) {
this.draw(degree);
}
},
// 获取输入的值
bindKeyInput(e) {
this.setData({
number: e.detail.value,
})
this.draw();
},
// 初始化画布
initCanvas() {
const query = wx.createSelectorQuery();
query.select('#myCanvas')
.fields({ node: true, size: true })
.exec((res) => {
const canvas = res[0].node;
const ctx = canvas.getContext('2d');
const dpr = wx.getSystemInfoSync().pixelRatio;
canvas.width = res[0].width * dpr;
canvas.height = res[0].height * dpr;
const { width, height } = res[0];
ctx.scale(dpr, dpr);
this.setData({ ctx });
this.draw();
})
},
// 内容绘制
draw(degree) {
this.data.ctx.clearRect(0, 0, 300, 300); // 清空画布内容
// 底圆 S
this.data.ctx.beginPath();
this.data.ctx.arc(150, 150, 120, 0.75 * Math.PI, 0.25 * Math.PI);
this.data.ctx.lineWidth = "20";
this.data.ctx.strokeStyle = "grey";
this.data.ctx.stroke();
// 底圆 E
// 覆盖层 S
let path = (this.data.number * 1.5 / this.data.total) + 0.75;
// 1.当number的值大于total的值时,遮罩层最大不能超过0.25(2.25)
// 2.degree代表的是圆弧拖动时的旋转角度
if (path > 2.25) {
path = 2.25
}
if (degree) {
path = degree;
}
this.data.ctx.beginPath();
this.data.ctx.arc(150, 150, 120, 0.75 * Math.PI, path * Math.PI, false);
this.data.ctx.lineWidth = "20";
this.data.ctx.strokeStyle = "orange";
this.data.ctx.stroke();
// 覆盖层 E
// 圆点
this.data.ctx.beginPath();
let x = -Math.cos(path * Math.PI) * 120;
let y = -Math.sin(path * Math.PI) * 120;
this.data.ctx.arc(150-x, 150-y, 20, 0, 2 * Math.PI);
this.data.ctx.fillStyle = 'green';
this.data.ctx.fill();
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
this.initCanvas();
},
})
以上为个人理解,如有错误,欢迎指正