美文网首页
微信小程序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