使用canvas绘制圆形进度条

1,690 阅读1分钟

今天咱们来使用canvas绘制一个带动画效果的圆形进度条

知识点包括

  • canvas的基本知识点
  • 常用的小技巧 缓动公式; 圆弧上某个点的xy坐标计算 ; requestAnimationFrame 的使用

具体的在代码里细说

<canvas id="cav" width="400" height="400"></canvas>

<script>

    class circleProgress {
        constructor(params) {
        
            /*
                需要传入的参数
                el canvas 元素
                progress  真实的进度比例 0 - 1  之间 100% 就是1;
                number 当前进度表示的数额
            */  
            const { el, progress, number } = params;
            
            //获取canvas 元素 及其 2d context
            const cav = document.querySelector(el);
            this.ctx = cav.getContext('2d');
            
            // 获取canvas 元素的宽高信息 用来计算圆形进度的 圆点坐标 
            const { width, height } = cav.getBoundingClientRect();
            this.w = width;
            this.h = height;
            this.x = width / 2;
            this.y = height / 2;
            this.r = 60;
            
            // Math.PI / 180 是很直观的度数换算公式 -> 度数 *  Math.PI / 180; 
            this.angle = Math.PI / 180;
            
            // 圆形进度的开始 和 结束 路径角度度数
            this.beginDeg = 140;
            this.endDeg = 40;
            
            // 计算出真实进度条的结束 路径 角度度数
            this.progressDeg = this.beginDeg + (360 - this.beginDeg + this.endDeg) * progress;
            
            this.aniDeg = this.beginDeg;
            this.number = number;
            this.move();
        }


        /*
            获取圆形上某个点的xy坐标
            公式:
            x = 圆心x坐标 + 半径 * Math.cos(点的角度*Math.PI/180)
            y = 圆心y坐标 + 半径 * Math.sin(点的角度*Math.PI/180)
        */ 
        getPointPos(deg) {
            const { ctx, x, y, r, angle } = this;
            return {
                xPos: x + r * Math.cos(deg * angle),
                yPos: y + r * Math.sin(deg * angle)
            }
        }

        // 绘制文字
        drawFont(font) {
            const { ctx, w, h } = this;
            // 获取文字的宽度 用已计算文字的x坐标 居中显示
            ctx.font = 'bold 30px arial';
            const fontW = ctx.measureText(font).width;
            ctx.fillStyle = '#00aeef';
            ctx.fillText(font, (w - fontW) / 2, h / 2 + 12)
        }

        // 绘制圆形
        drawCircle(color, endDeg) {
            const { ctx, x, y, r, angle } = this;
            ctx.beginPath();
            ctx.lineCap = "round";
            ctx.lineWidth = 8;
            ctx.strokeStyle = color;
            ctx.arc(x, y, r, 140 * angle, endDeg * angle);
            ctx.stroke();
            ctx.closePath();
        }



        // 绘制圆点
        drawPoint() {
            const { ctx, x, y, r, angle, aniDeg } = this;
            // 绘制圆形
            ctx.beginPath();
            ctx.fillStyle = "#00aeef";
            const { xPos, yPos } = this.getPointPos(aniDeg)
            ctx.arc(xPos, yPos, 8, 0, 2 * Math.PI);
            ctx.fill();
            ctx.closePath();
        }

        // 动画绘制
        move() {
            const { ctx, x, y, r, angle, w, h, progressDeg, number } = this;
            let font = 0;
            let last = false;
            const draw = () => {
                // canvas 动画美帧绘制前 都需要把上一帧清除掉
                ctx.clearRect(0, 0, w, h);
                // 绘制背景圆弧 
                this.drawCircle('#ccc', this.endDeg);
                // 绘制真实进度圆弧
                this.drawCircle('#00aeef', this.aniDeg);
                // 绘制真实进度圆点
                this.drawPoint();
                
                // 缓动公式  A = A + (B - A) / 速率;
                // 需要注意的是公式会无穷接近B 所以需要加一个临界判断;
                this.aniDeg = this.aniDeg + (progressDeg - this.aniDeg) / 20;
                font = font + (number - font) / 20;
                this.drawFont(Math.floor(font))
                if (last) { return; }
                if ((progressDeg - this.aniDeg > 1)) {
                    //使用requestAnimationFrame来实现动画 效果会更加细腻
                    requestAnimationFrame(draw)
                } else {
                    // 临界判断 最后一次绘制 达到目标进度;
                    last = true;
                    this.aniDeg = progressDeg;
                    font = number;
                    draw();
                }
            }
            draw();
        }
    }

    const goProgress = new circleProgress({
        el: '#cav',
        progress: 0.4,
        number: 4000
    })
    
</script>

最终效果

image.png