如果不想看步骤的可以直接看最后面有完整的代码

最近在做一个圆形的进度条,在网上看了一些例子有些地方不太理解,后来自己写了个一个分享一下

先上一个最终的效果

首先画一整个圆

    const cvsWitdh = 220    const cvsHeight = 220    const progess = 50 // 定义进度为50    const maxPro = 100  // 定义总进度为100    const r = 100 // 定义圆的半径为100    this.cvs.width = cvsWitdh    this.cvs.height = cvsHeight    const ctx = this.cvs.getContext('2d')    ctx.lineWidth = 10    ctx.strokeStyle = '#15496B'    ctx.arc(r + 10, r + 10, r, 0, 2 * Math.PI)     ctx.stroke() // 至此大圆画完
上面的代码需要注意的是 arc 方法的最后一侧参数是弧度(2)不是角度,画圆的起点是表的3点的位置开始画的不是12点位置

然后是画一个进度的圆弧

画圆弧度,主要是需要计算出起点的弧度终点的弧度

    ctx.beginPath()    ctx.lineCap = 'round'    // 下面是渐变的代码不需要的可以换成纯色    let grd = ctx.createLinearGradient(0, 0, 220, 220)    grd.addColorStop(0, 'red')    grd.addColorStop(1, 'blue')    ctx.strokeStyle = grd    const startRadian = progress >= maxPro ? 0 : Math.PI * 1.5    const rate = progress / maxPro    const endRadian = progress >= maxPro ? 2 * Math.PI : 2 * Math.PI * rate - Math.PI / 2    ctx.arc(r + 10, r + 10, r, startRadian, endRadian)    ctx.stroke()
上面的代码中 ctx.lineCap = 'round' 这个是设置最终绘制的线是带圆角的

起点的弧度计算方式

const startRadian = progess >= maxPro ? 0 : Math.PI * 1.5

我们希望点的起点位置是钟表12点钟位置,整个圆的弧度是 2==360° 推算得知12点钟的位置是 1.5==270°

终点的弧度计算方式

  const rate = progress / maxPro  const endRadian = progress >= maxPro ? 2 * Math.PI : 2 * Math.PI * rate - Math.PI / 2

const rate = progress / maxProo 得值为进度占圆的比率
2 * rate 就是进度所需要的弧度
由于 arc 方法画圆的起点是3点的方向而我们的起点是12点方向所以我们还需要减掉一个 Math.PI / 2最终就得出了我们上面的公式

由于当progress等于maxPro的时候算出来的终点等于我们的起点最终画的就会有问题,所以我们在计算起点终点的时候做了判断 progress >= maxPro 时画整圆

当前效果

动画实现

    let currentProgress = 1const timer = setInterval(() => {      if (currentProgress >= progress) {        currentProgress = progress        clearInterval(timer)      }      ctx.beginPath()      ctx.lineCap = 'round'      // 下面是渐变的代码不需要的可以换成纯色      let grd = ctx.createLinearGradient(0, 0, 220, 220)      grd.addColorStop(0, 'red')      grd.addColorStop(1, 'blue')      ctx.strokeStyle = grd      const startRadian = currentProgress >= maxPro ? 0 : Math.PI * 1.5      const rate = currentProgress / maxPro      const endRadian = currentProgress >= maxPro ? 2 * Math.PI : 2 * Math.PI * rate - Math.PI / 2      ctx.arc(r + 10, r + 10, r, startRadian, endRadian)      ctx.stroke()      currentProgress++    }, 10)

动画的实现也非常的简单,我们只需定义一个临时的进度 currentProgress 通过定时器每次累加这个进度知道与progress相等停止计时,期间每次绘制

完整的代码

我用react 写的所以直接把react的整个代码粘过来了,如果不需要的可以只拿绘图的那一部分

import React from 'react'export default class Test extends React.Component {  componentDidMount () {    this.renderProgress(30)  }  renderProgress (progress) {    const cvsWitdh = 220    const cvsHeight = 220    const maxPro = 100  // 定义总进度为100    const r = 100 // 定义圆的半径为100    this.cvs.width = cvsWitdh    this.cvs.height = cvsHeight    const ctx = this.cvs.getContext('2d')    ctx.lineWidth = 10    ctx.strokeStyle = '#15496B'    ctx.arc(r + 10, r + 10, r, 0, 2 * Math.PI) // 2 * Math.PI === 360 度 最后一个参数代表的是圆的弧度    ctx.stroke() // 至此大圆画完    if (progress === 0) {      return    }    let currentProgress = 1    const timer = setInterval(() => {      if (currentProgress >= progress) {        currentProgress = progress        clearInterval(timer)      }      ctx.beginPath()      ctx.lineCap = 'round'      // 下面是渐变的代码不需要的可以换成纯色      let grd = ctx.createLinearGradient(0, 0, 220, 220)      grd.addColorStop(0, 'red')      grd.addColorStop(1, 'blue')      ctx.strokeStyle = grd      const startRadian = currentProgress >= maxPro ? 0 : Math.PI * 1.5      const rate = currentProgress / maxPro      const endRadian = currentProgress >= maxPro ? 2 * Math.PI : 2 * Math.PI * rate - Math.PI / 2      ctx.arc(r + 10, r + 10, r, startRadian, endRadian)      ctx.stroke()      currentProgress++    }, 10)  }  render () {    return (      <div>        <br />        <br />        <canvas          ref={ref => {            this.cvs = ref          }}        />        <br />        <br />        <button onClick={() => {          this.renderProgress(60)        }}>重新loadprogress</button>      </div>    )  }}