微信小程序canvas2d实现可滑动的圆环形进度条

 

最近在搞一个微信小程序,有一个圆环的进度条,而且要求颜色要渐变的,本来想用秋云插件实现,但是秋云的插件不能滑动这个进度条,后面用canvas实现

成品效果图:

避坑:  

<canvas  id="myCanvas" type="2d"></canvas>

<canvas canvas-id="myCanvas"  ></canvas>

两个canvas标签,一个是新版的,一个是旧版的,如果指定了type,那么必须要指定id属性值,不然wx.createSelectorQuery()是无法获取到标签的

如果不指定type,使用旧版代码 wx.createCanvasContext('myCanvas')实现,会出现层级问题,canvas标签永远在最顶层,而且是无解的, 所以使用新的canvas2d去实现项目需求。

 

新canvas的宽高默认是300*150的,这个值不会根据css样式改变,需要手动设置

wx.createSelectorQuery()
    .select('#myCanvas').fields({ node: true, size: true })
    .exec((res) => {
      const canvas = res[0].node
      console.log(canvas.width, canvas.height);
    
    canvas.width = 250
    canvas.height = 250

    })

 

新版的canvas使用了web3C标准,推荐文档:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D

实现代码:

js代码 

Page({
  /**
   * 页面的初始数据
   */
  data: {
    progress: 74, // 当前转盘值
    isDragging: false, // 是否触摸中
    startAngle: -0.5 * Math.PI, // 开始角度
    endAngle: -0.5 * Math.PI, // 结束角度
    radius: 100, // 圆环半径
    centerX: 0,
    centerY: 0
  },
onReady() {
    this.setData({
      centerX: 250 / 2,
      centerY: 250 / 2
    })
    // 计算圆盘的初始值
    let endAngleVal:number = (this.data.progress - 25) / (5) * 0.1
    this.setData({
      endAngle: endAngleVal * Math.PI
    })
    setTimeout(() => {
      this.drawCircle();
    }, 200)
  },
drawCircle() {
    wx.createSelectorQuery()
    .select('#myCanvas').fields({ node: true, size: true })
    .exec((res) => {
      const canvas = res[0].node
      canvas.width = 250
      canvas.height = 250
      const ctx = canvas.getContext('2d')
      ctx.clearRect(0, 0, canvas.width, canvas.height) // 清除画布
      ctx.moveTo(0, 0.5); // 使用0.5增量对齐像素,消除锯齿
      // 背景圆弧渲染
      ctx.beginPath();
      ctx.arc(this.data.centerX, this.data.centerY, this.data.radius, 0, 2 * Math.PI);
      ctx.strokeStyle = '#a6a6a6'; // 设置描边颜色  
      ctx.lineWidth = 20; // 设置描边宽度  
      ctx.stroke(); 
      // 进度条渲染
      ctx.beginPath();
      ctx.arc(this.data.centerX, this.data.centerY, this.data.radius, this.data.startAngle, this.data.endAngle, false); 
      // 渐变颜色
      const gradient = ctx.createConicGradient(-10, 125, 125);
      // const gradient = ctx.createLinearGradient(100, 0, 125, 125);
      gradient.addColorStop(0, "#00a8e3");
      gradient.addColorStop(0.5, "#fde001");
      gradient.addColorStop(1, "#f00");
      // ctx.strokeStyle = '#007BFF'; // 描边颜色  
      ctx.strokeStyle = gradient;
      ctx.lineWidth = 20; // 设置描边宽度  
      ctx.lineCap = 'round' // 线条类型
      ctx.stroke(); // 描边路径,绘制环形进度条  

      // 滑动圆点
      ctx.beginPath();
      let whitePoint = {
        x: this.data.centerX + this.data.radius * Math.cos(this.data.endAngle),
        y: this.data.centerY + this.data.radius * Math.sin(this.data.endAngle)
      };
            ctx.strokeStyle = '#FFF'
            ctx.lineCap = 'round';
            ctx.lineWidth = 8;
            ctx.arc(whitePoint.x, whitePoint.y, 6, 0, 2 * Math.PI); // 空心圆
            ctx.stroke();
      ctx.closePath(); // 结束画布路径
    })
  },
updateAngle(x:number, y:number) {
    const dx = x - this.data.centerX;
    const dy = y - this.data.centerY;
    const angle = Math.atan2(dy, dx); // 计算触摸点与中心点的连线与x轴的夹角
    if(angle > -1.778 && angle < -1.56) {
      return
    }
    let progressVal = (angle / (2 * Math.PI) + 0.5) * 100
    let progressRange = parseFloat(Math.max(0, Math.min(100, progressVal)).toFixed(0)) 
    if (progressRange > 25) {
      progressRange -= 25
    } else {
      progressRange += 75
    }
    if (progressRange >= 100) {
      progressRange = 0
    }
    this.setData({
      endAngle : angle,
      // 确保进度在0到100之间  
      progress : progressRange
    })
  },
  doTouchStart(e:any) {//触摸开始
    this.setData({
      isDragging : true
    })
    this.updateAngle(e.touches[0].x, e.touches[0].y);
    this.drawCircle();
  },
  doTouchMove (e:any) {//触摸移动
    if (this.data.isDragging) {
      this.updateAngle(e.touches[0].x, e.touches[0].y);
      this.drawCircle();
    }
  },
doTouchend() {//触摸结束
    this.setData({
      isDragging : false
    })
  },
}

 

 

 wxml代码

<canvas  id="myCanvas" type="2d"  disable-scroll="{{true}}" bindtouchmove="doTouchMove" bindtouchstart="doTouchStart" bindtouchend="doTouchend" 
bindtouchcancel
="doTouchend" class="canvas-style"></canvas>
<text>{{
progress}}</text>

 

在微信开发者工具中显示正常,但是仍有问题

官网中说canvas2D是支持全部的web标准了,确实在开发者工具中没有错误,但是真机调试中有错误

 createConicGradient 这个方法报错了,这个方法是渐变颜色,圆椎形态渐变,很适合做圆环的渐变,

var ctx = canvas.getContext('2d');
                ctx.beginPath();
                const gradient = ctx.createConicGradient(-1.569, 200, 200);
                gradient.addColorStop(0, "#00a8e3");
                gradient.addColorStop(0.5, "#fde001aa");
                gradient.addColorStop(1, "#f00");
                ctx.fillStyle = gradient;
                ctx.fillRect(0, 0, 500, 500);
                ctx.closePath();

 

效果图:

奈何微信小程序不支持,后面我有想到了放这张锥形渐变图片上去,使用createPattern方法,先在html中试过了可以实现

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>可滑动的环形进度条</title>
        <style>
            canvas {
                border: 1px solid black;
            }
        </style>
    </head>
    <body>
        <canvas id="progressCanvas" width="300" height="300"></canvas>
        <style>
            #progressCanvas {
                /* background: linear-gradient(45deg, #ff7e5f, #feb47b, #fef); */
            }
        </style>
        <script>
            const canvas = document.getElementById('progressCanvas');
            const ctx = canvas.getContext('2d');

            let progress = 0; // 进度值,范围从0到100  
            let isDragging = false; // 是否正在拖动进度条  
            let startAngle = -0.5 * Math.PI; // 起始角度  
            let endAngle = startAngle; // 结束角度  
            const radius = 100; // 进度条半径  
            const centerX = canvas.width / 2; // 进度条中心X坐标  
            const centerY = canvas.height / 2; // 进度条中心Y坐标  

            // 绘制环形进度条  
            function drawRingProgress() {
                ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除画布  

                // 背景圆弧
                ctx.beginPath();
                ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
                ctx.strokeStyle = '#a6a6a6'; // 设置描边颜色  
                ctx.lineWidth = 20; // 设置描边宽度  
                ctx.stroke(); // 描边路径,绘制环形进度条  

                // 进度颜色
                ctx.beginPath();
                ctx.arc(centerX, centerY, radius, startAngle, endAngle, false); // 绘制环形进度条的路径  

                // 替换成渐变颜色
                let iamge = new Image()
                iamge.src = './a.png'
                // ctx.strokeStyle = '#007BFF'; // 设置描边颜色  
                ctx.strokeStyle = ctx.createPattern(iamge, "no-repeat")
                ctx.lineWidth = 20; // 设置描边宽度  
                ctx.lineCap = 'round'
                ctx.stroke(); // 描边路径,绘制环形进度条  

                // 滑动圆点
                ctx.beginPath();
                let whitePoint = {
                    x: centerX + radius * Math.cos(endAngle),
                    y: centerY + radius * Math.sin(endAngle)
                };

                ctx.strokeStyle = '#FFF'
                ctx.lineCap = 'round';
                ctx.lineWidth = 8;
                ctx.arc(whitePoint.x, whitePoint.y, 6, 0, 2 * Math.PI); // 空心圆
                ctx.stroke();
                ctx.closePath();
            }

            // 更新进度条的角度  
            function updateAngle(x, y) {
                const dx = x - centerX;
                const dy = y - centerY;
                const angle = Math.atan2(dy, dx); // 计算触摸点与中心点的连线与x轴的夹角  
                endAngle = angle;
                progress = (angle / (2 * Math.PI) + 0.5) * 100; // 将角度转换为百分比进度  
                progress = Math.max(0, Math.min(100, progress)); // 确保进度在0到100之间  
            }

            // 监听鼠标按下事件  
            canvas.addEventListener('mousedown', (e) => {
                isDragging = true;
                updateAngle(e.clientX, e.clientY);
                drawRingProgress();
            });

            // 监听鼠标移动事件  
            canvas.addEventListener('mousemove', (e) => {
                if (isDragging) {
                    updateAngle(e.clientX, e.clientY);
                    drawRingProgress();
                }
            });

            // 监听鼠标松开事件  
            canvas.addEventListener('mouseup', () => {
                isDragging = false;
            });

            // 监听鼠标离开画布事件  
            canvas.addEventListener('mouseleave', () => {
                isDragging = false;
            });

            // 初始绘制  
            drawRingProgress();
        </script>
    </body>
</html>

 

效果图

 后面再到微信小程序这边,我才想起来,微信小程序是没有 Image() 这个方法的,获取不到图片对象,没有办法通过图片去实现。

所以还是用了createLinearGradient方法。希望微信后续可以修改好这个问题。

 

posted @ 2024-04-23 10:59  花先生。  阅读(52)  评论(0编辑  收藏  举报