使用canvas绘制环形进度条

784 阅读3分钟

一、前景

今天遇到一个需求需要做一个环形进度条,本来想直接用Echarts这些完善的图表组件实现比较直接,但是发现这个小功能没必要使用这么高级功能的库(其实也是需求本身不负责,没必要使用),所以咱就开始从简出发,直接用canvas画一个。

二、效果图

主要有三部分:圆环背景、进度条、内部文字

image.png

三、实现步骤

  • 1、绘制外圈背景圆环,直接使用canvas的arc方法
 
 // arc(x, y, radius, 0, Math.PI * 2, false) 方法绘制
 // 此部分实现代码
 
 // 绘制背景圆环
 function backgroundCircle() {
        context.save()
        context.beginPath()
        context.lineWidth = pieLineWidth // 设置线宽
        const radius = center_x - context.lineWidth;
        context.lineCap = 'round'
        context.strokeStyle = '#fde6cc'
        context.arc(center_x, center_y, radius - pieLineWidth, 0, Math.PI * 2, false)
        context.stroke()
        context.closePath()
        context.restore()
    }
  • 2、绘制进度圆环,也是直接使用canvas的但是内圈圆环就要注意起点和终点的参数

这里放一个图供大家参考起止点坐标计算

image.png

// arc(x, y, radius, 0, Math.PI * 2, false) 方法绘制
// 此部分实现代码

// 绘制运动圆环
    function foregroundCircle(n) {
        context.save()
        context.lineWidth = pieLineWidth
        context.lineCap = 'round'
        var radius = center_x - context.lineWidth
        context.beginPath()
        context.strokeStyle = '#f78200'

        context.arc(
            center_x,
            center_y,
            radius - pieLineWidth,
            startPosition*Math.PI,
            startPosition*Math.PI + n * rad,
            movementDirection
        ) // 用于绘制圆弧context.arc(x坐标,y坐标,半径,起始角度,终止角度,顺时针/逆时针)
        context.stroke()
        context.closePath()
        context.restore()
    }
  • 3、绘制内部圆形背景和文字,同理圆形的可以直接使用 arc() 方法,但是我们画完了记得要填充;文字使用fillText() 方法传递字体文案,和具体坐标

  • 4、最后组合前三步进行绘制,恭喜你 进度条就已经出来了

四、完整源代码参考

  • html结构
    <canvas id="canvas" width="500" height="500" style="margin-top: 50px" ></canvas>
  • JS逻辑代码
const canvas = document.getElementById('canvas');
drawMain(canvas, 50,28,1.5,
    150,'7','Days','bold 40px sans-serif','bold 40px sans-serif',
    40,'white',10,false,true)

function drawMain(canvasId, percent,pieLineWidth,startPosition,insidePieRadius,
                  mainText,subText,mainTextStyle,subTextStyle,mainTextFs,fontColor,intervalSize,useAnimation,movementDirection){
    /**
     * 参数说明:
     * canvasId:画布id
     * percent:百分比
     * pieLineWidth:饼图线条宽度
     * startPosition:饼图起始位置
     * insidePieRadius:饼图内圆半径
     * mainText:主文字
     * subText:副文字
     * mainTextStyle:主文字样式  (40px sans-serif)
     * subTextStyle:副文字样式    (40px sans-serif)
     * mainTextFs:主文字大小
     * fontColor:文字颜色
     * intervalSize:间隔大小
     * useAnimation:是否使用动画
     * movementDirection:进度条方向   true是逆时针,false是顺时针
     */
    const context = canvasId.getContext('2d');
    const center_x = canvasId.width / 2;
    const center_y = canvasId.height / 2;
    const rad = (Math.PI * 2) / 100;
    let speed = 0;

    // 绘制背景圆圈
    function backgroundCircle() {
        context.save()
        context.beginPath()
        context.lineWidth = pieLineWidth // 设置线宽
        const radius = center_x - context.lineWidth;
        context.lineCap = 'round'
        context.strokeStyle = '#fde6cc'
        context.arc(center_x, center_y, radius - pieLineWidth, 0, Math.PI * 2, false)
        context.stroke()
        context.closePath()
        context.restore()
    }

    // 绘制运动圆环
    function foregroundCircle(n) {
        context.save()
        context.lineWidth = pieLineWidth
        context.lineCap = 'round'
        var radius = center_x - context.lineWidth
        context.beginPath()
        context.strokeStyle = '#f78200'

        context.arc(
            center_x,
            center_y,
            radius - pieLineWidth,
            startPosition*Math.PI,
            startPosition*Math.PI + n * rad,
            movementDirection
        ) // 用于绘制圆弧context.arc(x坐标,y坐标,半径,起始角度,终止角度,顺时针/逆时针)
        context.stroke()
        context.closePath()
        context.restore()
    }
    // 绘制内部文字背景
    function drawBg(){
        context.save()
        context.beginPath()
        context.arc(center_x,center_y,insidePieRadius,0,2*Math.PI);
        context.fillStyle="#f78200"
        context.fill();
    }

    // 绘制文字
    function text(topText,tip,interval,topTitleStyle,tipTitleStyle,fontSize,fontColor){
        context.fillStyle=fontColor
        context.font=topTitleStyle
        context.textAlign="center";
        context.fillText(topText,center_x,
            center_y - interval/4);
        context.font=tipTitleStyle
        context.fillText(tip,center_x,
            center_y + interval + fontSize);
    }

    // 绘图
    if(useAnimation){
        // 执行动画
        (function drawFrame() {
            if (speed <= percent) {
                window.requestAnimationFrame(drawFrame)
            } else {
                return false
            }
            context.clearRect(0, 0, canvasId.width, canvasId.height)
            backgroundCircle()
            foregroundCircle(speed)
            drawBg()
            text(mainText,subText,intervalSize,mainTextStyle,subTextStyle,mainTextFs,fontColor)
            if (speed >= percent) {
                speed ++
            } else {
                speed += 1
            }
        })()
    }else {
        context.clearRect(0, 0, canvasId.width, canvasId.height)
        backgroundCircle()
        foregroundCircle(percent)
        drawBg()
        text(mainText,subText,intervalSize,mainTextStyle,subTextStyle,mainTextFs,fontColor)
    }
}

感谢各位的阅读,也希望大佬们多多指教,小弟有写的不好的地方请留言指正。如果可以的话也希望得到各位一个小小的赞哦!谢谢!