小程序实现圆环拖动

1,078 阅读3分钟

最近要实现一个圆环拖动实现温度控制的需求,综合网上搜寻的各类文章,结合自己的理解,写下自己的思路

获取画布元素

使用小程序的画布组件并获取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)

底圆.png

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不改变,改变的是结束点的位置

01.png

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()

思路理解

输入数字绘制遮罩层

这个比较简单,就是存粹数学中的比例取值就可以了,但需要注意的是,当输入的值大于总值的时候,遮罩层的形状不能超出底层的范围

02.png

let number = 5; // 需要设定的值
let total = 30; // 总步数

path = 1.5 * number / total + 0.75;
鼠标拖动绘制遮罩层

这里需要用到atan2(这是我的理解,如有不对,欢迎指正)

atan2解释.png 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();
  },
})

以上为个人理解,如有错误,欢迎指正