Canvas绘制动态进度条圆环

14次阅读

共计 4407 个字符,预计需要花费 12 分钟才能阅读完成。

最终效果

一、定义初始变量

let radius = 140 // 外环半径
let thickness = 20 // 圆环厚度
let innerRadius = radius - thickness // 内环半径
let startAngle = -90 // 开始角度
let endAngle = 180 // 结束角度
let x = 0 // 圆心 x 坐标
let y = 0 // 圆心 y 坐标
let canvas = document.getElementById('tutorial');
canvas.width = 300;
canvas.height = 300;

let ctx = canvas.getContext('2d');
ctx.translate(canvas.width / 2, canvas.height / 2);// 将绘图原点移到画布中央
ctx.rotate(angle2Radian(225)) // 将画布旋转 225 度
ctx.fillStyle = "#f2d7d7"; // 初始填充颜色

二、工具方法

 // 计算圆环上点的坐标
function calcRingPoint(x, y, radius, angle) {let res = {}
    res.x = x + radius * Math.cos(angle * Math.PI / 180)
    res.y = y + radius * Math.sin(angle * Math.PI / 180)
    return res
}

// 弧度转角度
function radian2Angle(radian) {return 180 * radian / Math.PI}

// 角度转弧度
function angle2Radian(angle) {return angle * Math.PI / 180}

三、渲染方法

// 渲染函数
function renderRing(startAngle, endAngle) {ctx.beginPath();

    // 绘制外环
    ctx.arc(x, y, radius, angle2Radian(startAngle), angle2Radian(endAngle))

    // 计算外环与内环第一个连接处的中心坐标
    let oneCtrlPoint = calcRingPoint(x, y, innerRadius + thickness / 2, endAngle)

    // 绘制外环与内环第一个连接处的圆环
    ctx.arc(oneCtrlPoint.x, oneCtrlPoint.y, thickness / 2, angle2Radian(-90), angle2Radian(270))

    // 绘制内环
    ctx.arc(x, y, innerRadius, angle2Radian(endAngle), angle2Radian(startAngle), true)

    // 计算外环与内环第二个连接处的中心坐标
    let twoCtrlPoint = calcRingPoint(x, y, innerRadius + thickness / 2, startAngle)

    // 绘制外环与内环第二个连接处的圆环
    ctx.arc(twoCtrlPoint.x, twoCtrlPoint.y, thickness / 2, angle2Radian(-90), angle2Radian(270))

    ctx.fill()
    // ctx.stroke()}

具体思路:

为了方便,代码中使用弧度的地方一律由角度转为弧度

1. 绘制外环:

这一步最简单,直接按照官方的使用方法使用即可

2. 绘制外环与内环连接处的第一个圆环

首先算出,外环结束处与内环开始处中间点的坐标

计算圆环上点的坐标公式为:

x = x + radius Math.cos(angle Math.PI / 180)
y = y + radius Math.sin(angle Math.PI / 180)

代入以上公式可算出圆环上任意一点的坐标,然后再以此为圆心,圆环厚度 / 2 为半径 画圆环

3. 绘制内环

这一步只需将半径缩短,并将绘制外环的开始角度与结束角度调换即可

4. 绘制内环与外环连接处的第二圆环

和第二步同理,先计算出外环开始处与内环结束处中间点的坐标,然后再以此为圆心,圆环厚度 / 2 为半径 画圆环

5. 完成填充
到这一步,圆环就完成了

四、动态进度条

 // 进度条动画
ctx.fillStyle = "#e87c7c";
let tempAngle = startAngle
let twoEndAngle = 0
let step = (twoEndAngle - startAngle) / 100
let numberSpan = document.querySelector('.number')
let count = 0
let inter = setInterval(() => {if (tempAngle > twoEndAngle) {clearInterval(inter)
    } else {
        count++
        numberSpan.innerText = count
        tempAngle += step
    }
    renderRing(startAngle, tempAngle)
}, 16.7)

动态计算结束角度,然后设定一个计数器,重复执行渲染方法。

五、完整代码

<!DOCTYPE html>
<html lang="cn">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>canvas</title>
    <style>
        .ring {
            width: 300px;
            height: 300px;
            display: flex;
            align-items: center;
            justify-content: center;
            flex-direction: column;
            position: relative;
        }

        .fraction {
            position: absolute;
            font-size: 30px;
            font-weight: bold;
            color: red;
        }

        .small {
            font-size: 12px;
            font-weight: lighter;
        }

        .title {
            font-size: 20px;
            color: red;
            bottom: 40px;
            position: absolute;
        }
    </style>
</head>

<body>
    <div class="ring">
        <canvas id="tutorial"></canvas>
        <span class="fraction"><span class="number">0</span> <span class="small"> 分 </span> </span>
        <span class="title"> 服务分 </span>
    </div>

    <script>
        let radius = 140 // 外环半径
        let thickness = 20 // 圆环厚度
        let innerRadius = radius - thickness // 内环半径
        let startAngle = -90 // 开始角度
        let endAngle = 180 // 结束角度
        let x = 0 // 圆心 x 坐标
        let y = 0 // 圆心 y 坐标
        let canvas = document.getElementById('tutorial');
        canvas.width = 300;
        canvas.height = 300;

        let ctx = canvas.getContext('2d');
        ctx.translate(canvas.width / 2, canvas.height / 2);// 将绘图原点移到画布中央
        ctx.rotate(angle2Radian(225)) // 将画布旋转 225 度
        ctx.fillStyle = "#f2d7d7"; // 初始填充颜色

        renderRing(startAngle, endAngle)

        // 进度条动画
        ctx.fillStyle = "#e87c7c";
        let tempAngle = startAngle
        let twoEndAngle = 0
        let step = (twoEndAngle - startAngle) / 100
        let numberSpan = document.querySelector('.number')
        let count = 0
        let inter = setInterval(() => {if (tempAngle > twoEndAngle) {clearInterval(inter)
            } else {
                count++
                numberSpan.innerText = count
                tempAngle += step
            }
            renderRing(startAngle, tempAngle)
        }, 16.7)


        // 渲染函数
        function renderRing(startAngle, endAngle) {ctx.beginPath();

            // 绘制外环
            ctx.arc(x, y, radius, angle2Radian(startAngle), angle2Radian(endAngle))

            // 计算外环与内环第一个连接处的中心坐标
            let oneCtrlPoint = calcRingPoint(x, y, innerRadius + thickness / 2, endAngle)

            // 绘制外环与内环第一个连接处的圆环
            ctx.arc(oneCtrlPoint.x, oneCtrlPoint.y, thickness / 2, angle2Radian(-90), angle2Radian(270))

            // // 绘制内环
            ctx.arc(x, y, innerRadius, angle2Radian(endAngle), angle2Radian(startAngle), true)

            // 计算外环与内环第二个连接处的中心坐标
            let twoCtrlPoint = calcRingPoint(x, y, innerRadius + thickness / 2, startAngle)

            // 绘制外环与内环第二个连接处的圆环
            ctx.arc(twoCtrlPoint.x, twoCtrlPoint.y, thickness / 2, angle2Radian(-90), angle2Radian(270))

            ctx.fill()
            // ctx.stroke()}

        // 计算圆环上点的坐标
        function calcRingPoint(x, y, radius, angle) {let res = {}
            res.x = x + radius * Math.cos(angle * Math.PI / 180)
            res.y = y + radius * Math.sin(angle * Math.PI / 180)
            return res
        }

        // 弧度转角度
        function radian2Angle(radian) {return 180 * radian / Math.PI}

        // 角度转弧度
        function angle2Radian(angle) {return angle * Math.PI / 180}
    </script>
</body>

</html>

正文完
 0