美文网首页
微信小程序canvas自定义时间圆弧组件(点击、滑动、加减、倒计

微信小程序canvas自定义时间圆弧组件(点击、滑动、加减、倒计

作者: hao_developer | 来源:发表于2023-11-19 10:19 被阅读0次

    项目需求

    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

    \color{red}{angle是触摸点与圆的中心点计算得到弧度值,通过弧度值再转化为角度}\

    圆弧理解示意图.png
    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;
        }
      },
    })
    

    相关文章

      网友评论

          本文标题:微信小程序canvas自定义时间圆弧组件(点击、滑动、加减、倒计

          本文链接:https://www.haomeiwen.com/subject/fwapwdtx.html