您的当前位置:首页正文

【无标题】微信小程序的一些关于Canvas用法(实战篇)

2024-11-23 来源:个人技术集锦

需求

这次的需求有点难描述,设计图的一部分长这样:

需求分析

首先是这圆环了跟这个可移动的小圆了,问了百度的AI,没用Canvas,纯手搓的效果相当差,在屏幕中到处跑,根本束缚不住。
当时在搜相关有用的代码时发现别人用的Canvas代码,算是帮了大忙: ,因为比较新,对于我接下来使用谷歌浏览器的Sider使用的chatgpt3.5版AI时代码比较旧,有些代码已经被微信小程序弃用了,参考一下大佬的才算是转换过来。

1、圆环与可控制移动的点

问(PS.问了N次):微信小程序ui实现绕固定原点与半径的可控制移动的点,请提供代码
答:以下是一个示例代码,它可以在微信小程序中实现一个点围绕固定原点(圆心)按给定半径进行可控制的移动。用户可以通过滑动手指在屏幕上改变点的位置。

<view class="container">  
  <canvas canvas-id="circleCanvas" style="width: 300px; height: 300px;" bindtouchmove="onTouchMove" bindtouchend="onTouchEnd"></canvas>  
</view>  
  1. JS 文件
    在 JavaScript 文件中,编写逻辑以控制点的绘制和更新。
Page({  
  data: {  
    centerX: 150,  // 圆心的X坐标  
    centerY: 150,  // 圆心的Y坐标  
    radius: 100,   // 圆的半径  
    angle: 0       // 当前角度  
  },  

  onReady: function () {  
    this.drawCircle(); // 页面加载完成后绘制圆和点  
  },  

  drawCircle: function () {  
    const ctx = wx.createCanvasContext('circleCanvas', this);  
    const { centerX, centerY, radius } = this.data;  

    // 清空画布  
    ctx.clearRect(0, 0, 300, 300);  

    // 绘制圆  
    ctx.beginPath();  
    ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);  
    ctx.setStrokeStyle('#ccc'); // 设置圆圈颜色  
    ctx.setLineWidth(2); // 设置线宽  
    ctx.stroke(); // 绘制圆圈  

    // 绘制点  
    this.drawPoint(ctx); // 绘制圆周上的点  

    ctx.draw(); // 绘制到画布  
  },  

  drawPoint: function (ctx) {  
    const { centerX, centerY, radius, angle } = this.data;  

    // 计算点在圆周上的位置  
    const pointX = centerX + radius * Math.cos(angle); // 点的X坐标  
    const pointY = centerY + radius * Math.sin(angle); // 点的Y坐标  

    // 绘制点  
    ctx.beginPath();  
    ctx.arc(pointX, pointY, 10, 0, 2 * Math.PI); // 点的半径为10  
    ctx.setFillStyle('#000'); // 设置点的颜色  
    ctx.fill(); // 填充点  
  },  

  onTouchMove: function (e) {  
    const touch = e.touches[0]; // 获取手指触控的位置  
    const { centerX, centerY } = this.data;  

    // 计算当前触摸点与圆心的距离  
    const dx = touch.x - centerX; // 使用 touch.x 而不是 clientX  
    const dy = touch.y - centerY; // 使用 touch.y 而不是 clientY  
    const distance = Math.sqrt(dx * dx + dy * dy);  

    // 计算当前角度  
    let angle = Math.atan2(dy, dx);  
    if (distance > this.data.radius) {   
      // 如果超过半径,则按半径限制  
      angle = Math.atan2(dy / distance * this.data.radius, dx / distance * this.data.radius);  
    }  

    this.setData({ angle }); // 更新当前角度  

    this.drawCircle(); // 重新绘制  
  },  

  onTouchEnd: function () {  
    // 可以根据需要在触摸结束时添加逻辑  
  }  
});  
  1. CSS 文件
    在 CSS 文件中添加一些基本的样式以美化界面。
.container {  
  display: flex;  
  align-items: center;  
  justify-content: center;  
  height: 100vh; /* 充满屏幕高度 */  
}  

代码说明
数据初始化: 我们定义了圆心的位置(centerX 和 centerY)、圆的半径(radius)以及当前的角度(angle)。

绘制圆和点:

在 drawCircle() 方法中,首先清空画布,然后描绘一个灰色的圆和当前点的黑色圆。
drawPoint(ctx) 函数用于计算点的位置并将其绘制到圆周上。
手指移动控制:

onTouchMove() 方法获取当前触摸点的坐标,并计算这个点到圆心的距离和角度。
使用 Math.atan2(dy, dx) 计算当前点的角度,并根据实际情况限制该点在指定半径内移动。
样式设置: 简单的 CSS 使得界面居中。

2、渐变色的圆

问:点的颜色是从上往下渲染而成
答:如果你希望点的颜色在整个圆形上实现从上往下的渐变效果,可以使用 createRadialGradient 创建一个圈的径向渐变效果,但为更符合“从上往下渲染”的效果,我们可以考虑使用 createLinearGradient 方法来创建一个线性渐变,并将其应用于绘制的圆形。

实现从上往下的线性渐变
以下是一个示例代码,展示如何实现这个效果:

drawPoint: function (ctx) {  
  const { centerX, centerY, radius, angle } = this.data;  

  // 计算点在圆周上的位置  
  const pointX = centerX + radius * Math.cos(angle); // 点的X坐标  
  const pointY = centerY + radius * Math.sin(angle); // 点的Y坐标  

  // 创建从上到下的线性渐变  
  const gradient = ctx.createLinearGradient(pointX, pointY - 10, pointX, pointY + 10); // 从点上方到下方的渐变  
  gradient.addColorStop(0, 'red');  // 上部为红色  
  gradient.addColorStop(1, 'blue'); // 下部为蓝色  

  // 绘制点  
  ctx.beginPath();  
  ctx.arc(pointX, pointY, 10, 0, 2 * Math.PI); // 点的半径为10  
  ctx.setFillStyle(gradient); // 使用创建的渐变色  
  ctx.fill(); // 填充点  
}  

解释代码
创建线性渐变:

ctx.createLinearGradient(pointX, pointY - 10, pointX, pointY + 10):创建一个从 pointY - 10(点的上方)到 pointY + 10(点的下方)的线性渐变。
addColorStop(0, ‘red’) 设置渐变的开始颜色(上部为红色)。
addColorStop(1, ‘blue’) 设置渐变的结束颜色(下部为蓝色)。
绘制和填充:

使用 ctx.arc() 绘制一个以 (pointX, pointY) 为中心、半径为 10 的圆形。
使用 ctx.setFillStyle(gradient) 将刚才创建的渐变颜色应用到该圆形上并使用 ctx.fill() 进行填充。

其实到这没有得到我想要的效果,只是一个从上到下的渐变色的圆,想要渐变色仅呈现在圆环,只需要再其圆上面再绘制一个纯色小圆即可。

3、中间滚动的数字

(1)中间的圆角矩形

问:微信小程序使用画布canvas画带圆角的矩形
答:在微信小程序中绘制带圆角的矩形可以通过 Canvas API 手动实现。由于 Canvas API 直接不支持绘制圆角矩形,需要通过贝塞尔曲线来实现。以下是一个完整的示例,展示如何在画布上绘制带圆角的矩形。

 drawRoundedRect: function (ctx, x, y, width, height, radius) {  //x、y分别是开始绘制的x、y坐标,width、height是矩形的宽度与高度,radius是圆角半径
    ctx.beginPath();  

    // 绘制四个圆角  
    ctx.moveTo(x + radius, y); // 移动到左上角  
    ctx.lineTo(x + width - radius, y); // 绘制上边  
    ctx.quadraticCurveTo(x + width, y, x + width, y + radius); // 右上角  
    ctx.lineTo(x + width, y + height - radius); // 右边  
    ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); // 右下角  
    ctx.lineTo(x + radius, y + height); // 下边  
    ctx.quadraticCurveTo(x, y + height, x, y + height - radius); // 左下角  
    ctx.lineTo(x, y + radius); // 左边  
    ctx.quadraticCurveTo(x, y, x + radius, y); // 左上角  

    ctx.closePath(); // 闭合路径  
    ctx.setFillStyle('blue'); // 设置填充颜色  
    ctx.fill(); // 填充矩形  
  }  
(2)滚动数字效果

问:微信小程序使用画布canvas做手指触摸滑动控制滚动数字效果,数字大小范围为0~9,同时展示大于与小于自身数字2与等于自身数字的数字
答:可以通过 canvas 实现手指触摸滑动控制一个在范围 0~9 内滚动的数字,并且在画布上展示当前值的上下两个数字(大于与小于当前数字的数字)以及当前数字本身。

  draw: function (ctx) {  
    ctx.clearRect(0, 0, 300, 150); // 清空画布  
    ctx.setFontSize(48);  
    ctx.setTextAlign('center');  
    ctx.setFillStyle('black');  

    // 绘制当前数字  
    ctx.fillText(this.data.currentNumber, 150, 65);  
    
    // 绘制比当前数字小的数字(如果大于0)  
    if (this.data.currentNumber > 0) {  
      ctx.fillText(this.data.currentNumber - 1, 150, 110); // 下一个数字  
    }  
    
    // 绘制比当前数字大的数字(如果小于9)  
    if (this.data.currentNumber < 9) {  
      ctx.fillText(this.data.currentNumber + 1, 150, 30); // 上一个数字  
    }  

    ctx.draw(); // 更新画布  
  },  

  touchStart: function (e) {  
    this.setData({  
      lastTouchY: e.touches[0].clientY // 记录触摸开始的 Y 坐标  
    });  
  },  

  touchMove: function (e) {  
    const deltaY = this.data.lastTouchY - e.touches[0].clientY; // 计算手指滑动的距离  
    this.setData({  
      lastTouchY: e.touches[0].clientY // 更新 Y 坐标  
    });  

    let newNumber = this.data.currentNumber + Math.floor(deltaY / 10); // 每滑动10px调整1个数字  
    newNumber = Math.max(0, Math.min(9, newNumber)); // 限制范围在0-9之间  

    if (newNumber !== this.data.currentNumber) {  
      this.setData({ currentNumber: newNumber }); // 更新当前数字  
      const ctx = wx.createCanvasContext('myCanvas', this);  
      this.draw(ctx); // 重新绘制  
    }  
  },

截止目前所遇问题

1、如何区分触摸数字滚动区域与圆点区域

问:微信小程序可以在画布canvas上再建1个画布吗,我需要解决2种触摸事件除了新建画布还有其他办法吗
答:在微信小程序中,不能在一个 上直接再创建一个子 ,因为 是一个 HTML 元素,每个页面只能有一个 组件。如果需要处理两种不同的触摸事件,通常的做法是通过绘制不同的图形或使用不同的区域来区分触摸事件。

方法一:使用不同的区域
你可以在同一个 上绘制多个区域,并根据触摸的坐标判断触摸事件属于哪个区域。例如,对于滚动数字和另一个交互区域,可以这样处理:

定义不同的区域。
在触摸事件中判断触摸的位置。
根据触摸位置调用不同的逻辑。
代码示例
假设要实现一个数字和一个可以点击的按钮区域:

<view>  
  <canvas canvas-id="myCanvas" style="width: 300px; height: 150px;" bindtouchstart="touchStart" bindtouchmove="touchMove" bindtouchend="touchEnd"></canvas>  
  <view>滑动所控制的数字: {{currentNumber}}</view>  
</view>  
Page({  
  data: {  
    currentNumber: 0,  
    maxNumber: 100,  
    minNumber: 0,  
    lastTouchY: 0,  
    buttonPressed: false,  
  },  

  onReady: function () {  
    const ctx = wx.createCanvasContext('myCanvas', this);  
    this.draw(ctx);  
  },  

  draw: function (ctx) {  
    ctx.clearRect(0, 0, 300, 150);  
    ctx.setFontSize(48);  
    ctx.setTextAlign('center');  
    ctx.setFillStyle('black');  
    ctx.fillText(this.data.currentNumber, 150, 65);  

    // 绘制按钮区域  
    ctx.setFillStyle(this.data.buttonPressed ? 'lightgray' : 'gray');  
    ctx.fillRect(100, 100, 100, 40);  
    ctx.setFillStyle('white');  
    ctx.fillText('Button', 150, 130);  
    
    ctx.draw();  
  },  

  touchStart: function (e) {  
    this.setData({  
      lastTouchY: e.touches[0].clientY  
    });  

    // 判断触摸点是否在按钮区域  
    const touchX = e.touches[0].clientX;  
    const touchY = e.touches[0].clientY;  
    if (touchX >= 100 && touchX <= 200 && touchY >= 100 && touchY <= 140) {  		//重点
      this.setData({ buttonPressed: true });  
    }  
  },  

  touchMove: function (e) {  
    const deltaY = e.touches[0].clientY - this.data.lastTouchY;  
    this.setData({  
      lastTouchY: e.touches[0].clientY  
    });  

    // 在用户没有触摸按钮时更新数字  
    if (!this.data.buttonPressed) {  
      this.setData({  
        currentNumber: Math.max(this.data.minNumber, Math.min(this.data.maxNumber, this.data.currentNumber - deltaY))  
      });  
    }  

    const ctx = wx.createCanvasContext('myCanvas', this);  
    this.draw(ctx);  
  },  

  touchEnd: function (e) {  
    // 触摸结束,可能需要重置按钮状态  
    this.setData({ buttonPressed: false });  
    const ctx = wx.createCanvasContext('myCanvas', this);  
    this.draw(ctx);  
  },  
});  

代码说明
draw 方法同时绘制了数字和一个按钮。
touchStart 方法判断触摸点是否在按钮区域。如果在按钮区域则将 buttonPressed 设置为 true。
touchMove 方法检测触摸点,如果未触摸按钮,则更新数字。
touchEnd 方法结束触摸时重置按钮状态,并重新绘制画布。
方法二:使用状态管理
另一种方式是通过状态来管理不同的交互,例如设置标志位(如当前是否正在滑动数字或点击按钮),在各自的触摸事件中使用这些状态来决定如何响应。

小结
在同一个 上处理多个触摸事件的关键在于:
通过界面上的不同区域来区分事件。
根据触摸的位置和当前状态来决定相应的行为。

2、如何让圆点拉动时同步改变数字,反之亦然

这个倒是没问AI,单纯手搓,根据自己实际需求一点点调即可。个人只是做了手指触摸移动后离开屏幕的相关同步,也就是大部分工作放在了touchEnd 中。

个人代码

最后得到的代码如下:
Typescript(众所周知,跟JS基本一样)

//1vmin的值
let vminScale = Math.min(wx.getWindowInfo().windowWidth, wx.getWindowInfo().windowHeight) / 100;
//屏幕宽度
let screenWidth = wx.getWindowInfo().windowWidth;
//屏幕高度
let screenHeight = wx.getWindowInfo().windowHeight;
//1rpx
let aspectRatio = screenWidth / 750;
//绕圈运动的半径105 * aspectRatio - 3.5 * vminScale
let circleRadius = 110;//0.14 * screenWidth
let initStatusX = screenWidth / 2 + circleRadius;
let initStatusY = screenHeight / 2 - 3.5 * vminScale;
let startMoveX;
let startMoveY;

Page({

  /**
   * 页面的初始数据
   */
  data: {
    radius: 0.14 * screenWidth - 5.5 * vminScale,
    angle: 0,
    old_angle: 0,
    centerX: 0.14 * screenWidth,
    centerY: 0.14 * screenWidth,
    isMoving: false,
    currentNumber: 90,    //滑动代表的数据
    currentNumber1: 0,    // 当前显示的数字  
    maxNumber1: 3,       // 目标数字上限  
    minNumber1: 0,        // 目标数字下限
    currentNumber2: 6,
    maxNumber2: 9,
    minNumber2: 0,
    currentNumber3: 0,
    maxNumber3: 9,
    minNumber3: 0,
    lastTouchY: 0,       // 上一次触摸 Y 坐标 
    circlePressed: false,
    number1Pressed: false,
    number2Pressed: false,
    number3Pressed: false,
    moveDistance: 0,   //拖动的距离
    moveAngle: 0,
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad() {
    this.splitNumber(this.data.currentNumber);
  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady() {
    this.drawCircle(); // 页面加载完成后绘制圆和点
  },
  //三位数剥离
  splitNumber(number) {
    let hundred = Math.floor(number / 100);    // 获取百位
    let ten = Math.floor((number % 100) / 10);    // 获取十位
    let one = number % 10;    // 获取个位
    this.setData({
      currentNumber1: hundred,
      currentNumber2: ten,
      currentNumber3: one,
    });
  },

  drawCircle: function () {
    let query = wx.createSelectorQuery()			//当前的Canvas用法, const ctx ==wx.createCanvasContext('circleCanvas', this); 不顶用,会报错
    query.select('#myCanvas')
      .fields({ node: true, size: true })
      .exec((res) => {
        const canvas = res[0].node
        // console.log('666', canvas);
        let ctx = canvas.getContext("2d")
        // 清空画布  
        ctx.clearRect(0, 0, 0.28 * screenWidth, 0.28 * screenWidth);

        // Canvas 画布的实际绘制宽高
        const width = res[0].width
        const height = res[0].height

        // 初始化画布大小(省略的话对于不同手机会有不同缩放)
        const dpr = wx.getWindowInfo().pixelRatio
        canvas.width = width * dpr
        canvas.height = height * dpr
        ctx.scale(dpr, dpr)

        // 绘制圆  
        ctx.beginPath();
        ctx.arc(this.data.centerX, this.data.centerY, this.data.radius, 0, 2 * Math.PI);
        ctx.lineWidth = 8 // 设置线宽 
        ctx.strokeStyle = '#FFFFFF30' // 设置圆圈颜色 
        ctx.stroke(); // 绘制圆圈  

        // 设置字体颜色  
        ctx.fillStyle = 'white';
        ctx.textAlign = "center"
        // 设置字体大小  
        ctx.font = '12px Arial'; // 字体大小为12px(这个要加px)
        // 绘制文本  
        ctx.fillText('0', 105 * aspectRatio - 5, 105 * aspectRatio - this.data.radius + 5 * vminScale);
        ctx.fillText('90', 210 * aspectRatio - 10 * vminScale, 105 * aspectRatio);
        ctx.fillText('180', 105 * aspectRatio, 210 * aspectRatio - 9 * vminScale);
        ctx.fillText('270', 10 * vminScale, 105 * aspectRatio);

        this.drawRoundedRect(ctx, 105 * aspectRatio - 60, 105 * aspectRatio - 17, 120, 30, 10);

        //3个现有数字(可滑动)
        ctx.fillStyle = 'white';
        ctx.textAlign = "center";
        ctx.font = '16px Arial';
        ctx.fillText(this.data.currentNumber1, 105 * aspectRatio - 45, 105 * aspectRatio);
        ctx.fillText(this.data.currentNumber2, 105 * aspectRatio - 5, 105 * aspectRatio);
        ctx.fillText(this.data.currentNumber3, 105 * aspectRatio + 35, 105 * aspectRatio);

        //3个数字的上下数字
        // 绘制比当前数字小的数字(如果大于0)  
        if (this.data.currentNumber1 > 0) {
          ctx.fillText(this.data.currentNumber1 - 1, 105 * aspectRatio - 45, 105 * aspectRatio - 25); // 下一个数字  
        }
        // 绘制比当前数字大的数字(如果小于3)  
        if (this.data.currentNumber1 < 3) {
          ctx.fillText(this.data.currentNumber1 + 1, 105 * aspectRatio - 45, 105 * aspectRatio + 25); // 上一个数字  
        }

        if (this.data.currentNumber2 > 0) {
          ctx.fillText(this.data.currentNumber2 - 1, 105 * aspectRatio - 5, 105 * aspectRatio - 25); // 下一个数字
          if (this.data.currentNumber2 > 1) {
            ctx.fillText(this.data.currentNumber2 - 2, 105 * aspectRatio - 5, 105 * aspectRatio - 50); // 下第2个数字
          }
        }
        // 绘制比当前数字大的数字(如果小于9)  
        if (this.data.currentNumber2 < 9) {
          ctx.fillText(this.data.currentNumber2 + 1, 105 * aspectRatio - 5, 105 * aspectRatio + 25); // 上一个数字
          if (this.data.currentNumber2 < 8) {
            ctx.fillText(this.data.currentNumber2 + 2, 105 * aspectRatio - 5, 105 * aspectRatio + 50);
          }
        }

        if (this.data.currentNumber3 > 0) {
          ctx.fillText(this.data.currentNumber3 - 1, 105 * aspectRatio + 35, 105 * aspectRatio - 25); // 下一个数字
          if (this.data.currentNumber3 > 1) {
            ctx.fillText(this.data.currentNumber3 - 2, 105 * aspectRatio + 35, 105 * aspectRatio - 50); // 下第2个数字
          }
        }
        // 绘制比当前数字大的数字(如果小于9)  
        if (this.data.currentNumber3 < 9) {
          ctx.fillText(this.data.currentNumber3 + 1, 105 * aspectRatio + 35, 105 * aspectRatio + 25); // 上一个数字
          if (this.data.currentNumber3 < 8) {
            ctx.fillText(this.data.currentNumber3 + 2, 105 * aspectRatio + 35, 105 * aspectRatio + 50);
          }
        }

        // 绘制点  
        this.drawPoint(ctx); // 绘制圆周上的点  

      });
  },

  drawPoint: function (ctx) {

    // 计算点在圆周上的位置  
    const pointX = this.data.centerX + this.data.radius * Math.cos(this.data.angle); // 点的X坐标  
    const pointY = this.data.centerY + this.data.radius * Math.sin(this.data.angle); // 点的Y坐标  

    // 创建渐变对象  
    //   const gradient = ctx.createRadialGradient(pointX, pointY, 0, pointX, pointY, 10); // 从点中心向外半径10  
    //   gradient.addColorStop(0, 'rgba(255, 0, 0, 1)'); // 中心颜色为不透明红色  
    // gradient.addColorStop(1, 'rgba(0, 0, 255, 0)'); // 边缘为完全透明蓝色  

    // 创建从上到下的线性渐变  
    const gradient = ctx.createLinearGradient(pointX, pointY - 10, pointX, pointY + 10); // 从点上方到下方的渐变  
    gradient.addColorStop(0, '#E166C3');   
    gradient.addColorStop(1, '#6E4ACB'); 

    // 绘制点(就是渐变的圆)  
    ctx.beginPath();
    ctx.arc(pointX, pointY, 10, 0, 2 * Math.PI); // 点的半径为10  
    ctx.fillStyle = gradient; // 设置点的颜色  33374F
    ctx.fill(); // 填充点 

    // 绘制点  
    ctx.beginPath();
    ctx.arc(pointX, pointY, 8, 0, 2 * Math.PI); // 点的半径为10  
    ctx.fillStyle = "#33374F"; // 设置点的颜色 
    ctx.fill(); // 填充点 
  },
  //圆角矩形
  drawRoundedRect: function (ctx, x, y, width, height, radius) {
    ctx.beginPath();
    ctx.moveTo(x + radius, y);
    ctx.lineTo(x + width - radius, y);
    ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
    ctx.lineTo(x + width, y + height - radius);
    ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
    ctx.lineTo(x + radius, y + height);
    ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
    ctx.lineTo(x, y + radius);
    ctx.quadraticCurveTo(x, y, x + radius, y);
    ctx.closePath();
    // 设置矩形填充颜色  
    ctx.fillStyle = '#FFFFFF10';
    ctx.fill();
  },

  onTouchStart: function (e) {
    this.setData({
      old_angle: this.data.angle,
      lastTouchY: e.touches[0].clientY
    });
    console.log('开始触摸时', this.data.lastTouchY);
    // 判断触摸点所在区域  
    const touchX = e.touches[0].clientX;
    const touchY = e.touches[0].clientY;
    console.log('touchX', touchX);
    console.log('touchY', touchY);
    if (touchX > 380 && touchX <= 420 && touchY >= 130 && touchY <= 205) {
      this.setData({ number1Pressed: true });
    } else if (touchX > 420 && touchX <= 460 && touchY >= 130 && touchY <= 205) {
      this.setData({ number2Pressed: true });
    } else if (touchX > 460 && touchX <= 500 && touchY >= 130 && touchY <= 205) {
      this.setData({ number3Pressed: true });
    } else if ((touchX >= 320 && touchX <= 380 && touchY >= 55 && touchY <= 290) ||
      (touchX > 380 && touchX <= 500 && ((touchY >= 55 && touchY < 130) || (touchY > 205 && touchY <= 290))) ||
      (touchX > 500 && touchX <= 550 && touchY >= 55 && touchY <= 290)) {
      this.setData({ circlePressed: true });
    }
  },

  onTouchMove: function (e) {
    const touch = e.touches[0]; // 获取手指触控的位置  
    const { centerX, centerY } = this.data;
    const deltaY = e.touches[0].clientY - this.data.lastTouchY; // 计算滑动的距离 
    let sumY = this.data.moveDistance + deltaY;

    if (this.data.circlePressed) {
      // 计算当前触摸点与圆心的距离  
      const dx = touch.x - centerX; // 使用 touch.x 而不是 clientX  
      const dy = touch.y - centerY; // 使用 touch.y 而不是 clientY  
      const distance = Math.sqrt(dx * dx + dy * dy);

      // 计算当前角度  
      let angle = Math.atan2(dy, dx);
      console.log('计算当前角度', angle)
      // if (distance > this.data.radius) {			//这段代码真狗,会导致角度突变
      //   // 如果超过半径,则按半径限制  
      //   angle = Math.atan2(dy / distance * this.data.radius, dx / distance * this.data.radius) - 90;
      //   console.log('如果超过半径,则按半径限制',angle);
      // }
      let angleNumber = angle - this.data.old_angle;
      let nextNumber = this.data.currentNumber;
      let sumAngle = angleNumber;
      console.log('角度变化中', sumAngle)

      this.setData({
        angle: angle,
        moveAngle: sumAngle
      }); // 更新当前角度 
      this.splitNumber(nextNumber);
    } else if (this.data.number1Pressed) {
      this.setData({
        lastTouchY: e.touches[0].clientY // 更新最后触摸的 Y 坐标  
      });

      // 根据滑动方向增加或减少数字 
      let nextNumber = this.data.currentNumber1;
      if (sumY >= 25) {
        --nextNumber;
      } else if (sumY <= -25) {
        ++nextNumber;
      }
      if (nextNumber > 3) {
        nextNumber = 3;
      } else if (nextNumber < 0) {
        nextNumber = 0;
      }
      this.setData({
        currentNumber1: nextNumber
      });
    } else if (this.data.number2Pressed) {
      this.setData({
        lastTouchY: e.touches[0].clientY // 更新最后触摸的 Y 坐标  
      });
      let nextNumber = this.data.currentNumber2;
      if (sumY >= 25) {
        --nextNumber;
      } else if (sumY <= -25) {
        ++nextNumber;
      }
      if (nextNumber > 9) {
        nextNumber = 9;
      } else if (nextNumber < 0) {
        nextNumber = 0;
      }
      this.setData({
        currentNumber2: nextNumber
      });
    } else if (this.data.number3Pressed) {
      this.setData({
        lastTouchY: e.touches[0].clientY // 更新最后触摸的 Y 坐标  
      });
      let nextNumber = this.data.currentNumber3;
      if (sumY >= 25) {
        --nextNumber;
      } else if (sumY <= -25) {
        ++nextNumber;
      }
      if (nextNumber > 9) {
        nextNumber = 9;
      } else if (nextNumber < 0) {
        nextNumber = 0;
      }
      this.setData({
        currentNumber3: nextNumber
      });
    }
    this.setData({
      moveDistance: Math.abs(sumY) >= 25 ? 0 : sumY
    })
    this.drawCircle(); // 重新绘制   
  },

  onTouchEnd: function () {
    console.log('========触摸结束(在此才同步两者的信息)========');
    let nextNumber = this.data.currentNumber;
    let nextangle = this.data.angle;
    if (this.data.circlePressed) {
      let angleNumber = this.data.moveAngle / Math.PI * 180;
      if (Math.abs(angleNumber) >= 360) {
        angleNumber = angleNumber % 360;
      }
      if (angleNumber >= 0) {
        nextNumber += Math.floor(angleNumber);
      } else if (angleNumber < 0) {
        nextNumber += Math.ceil(angleNumber);
      }
      console.log('角度变化', angleNumber);
    } else {
      nextNumber = this.data.currentNumber1 * 100 + this.data.currentNumber2 * 10 + this.data.currentNumber3;
    }    
    nextangle = (nextNumber - 90) * Math.PI / 180;
    if (nextNumber < 0) {
      nextNumber += 360;
    } else if (nextNumber >= 360) {
      nextNumber = nextNumber % 360;
    }
    console.log('新节拍数', nextNumber);
    console.log('新角度', nextangle);
    this.setData({
      isDragging: false,
      angle: nextangle,
      currentNumber: nextNumber,
      number1Pressed: false,
      number2Pressed: false,
      number3Pressed: false,
      circlePressed: false,
      moveDistance: 0,
      moveAngle: 0,
    }); // 拖动结束  
    this.splitNumber(this.data.currentNumber);
    this.drawCircle();
  },
})

WXML

<view class="container">
      <canvas type="2d" id="myCanvas" style="z-index: 99;width: 210rpx; height: 210rpx;" bindtouchstart="onTouchStart" bindtouchmove="onTouchMove" bindtouchend="onTouchEnd"></canvas>
</view>

LESS(也就是CSS)

    .container {
      width: 230rpx;
      height: 230rpx;
      display: flex;
      flex-direction: column;
      align-items: center;
      z-index: 95;
    }

OK,收工,希望对各位有所帮助。

显示全文