JavaScript通过canvas绘制可以自定义颜色、文字效果的动态环形进度条

前言

早些时候写过的一个大屏项目,其中的环形进度条是使用css来实现的,但是在不同分辨率的情况下会有点问题,如下图所示:**

在这里插入图片描述

为避免这样的问题,现在尝试对以前的功能进行修改,使用canvas进行绘制,并能自定义颜色和文字样式。

1.获取目标div大小,并创建画布,定义需要的值:

  <div id="myCanvas" style="width:100px;height:100px"></div>
  <script>
 //  获取 canvas 元素和 2D 绘图上下文对象
    const container = document.getElementById(arg.container);
     if (!container) {
      // 容器不存在
      throw new Error('容器不存在')
    }
     const canvas = document.createElement('canvas')
     // 设置画布在页面上的大小
     canvas.style = `width:${container.clientWidth}px; height:${container.clientHeight}px;`;
    const ctx = canvas.getContext('2d');
     // 设置画布内容是容器的两倍不容易出现图像边缘不清晰
    canvas.width = container.clientWidth * 2
    canvas.height = container.clientHeight * 2
    container.append(canvas)
        //  设置进度条背景颜色和前景颜色
    // 进度条宽度
    const progressSize = arg.progressSize || 23
    // 圆大小
    const circleSize = canvas.width / 2
    // 中间圆大小
    const circle_center = circleSize - progressSize
    // 圆xy坐标
    const cx = canvas.width / 2
    const cy = canvas.height / 2
    // 剪切同心圆需要的
    let step = 0.8

    // 圆的初始角度和结束时的角度
    let inputData = typeof +arg.data === 'number' && !isNaN(+arg.data) ? arg.data : 0
    // 进度条从0-100的角度是从0.7-2.3
    const initialProgress = 0.7
    const totalFrames = 2.3;
    let progressValue = initialProgress

    // 根据传进来的数值,计算目前的角度
    let angle = (totalFrames - initialProgress) / 100
    let progressEnd = initialProgress + angle * inputData;

2.绘制圆,因为我们所需要的圆是缺了一角的,使用arc() 方法进行画圆,如下图所示
在这里插入图片描述
根据官方的api,设置开始角度为0.7,结束角度为2.3,绘制进度条背景圆

    // 绘制进度条背景
    ctx.beginPath();
    ctx.moveTo(cx, cy)
    ctx.fillStyle = arg.backgroundColor;
    ctx.arc(cx, cy, circleSize, initialProgress * Math.PI, totalFrames * Math.PI)
    ctx.fill()

效果如下所示:
在这里插入图片描述
3.使用同样的方法绘画前景圆,并添加动画效果:

      // 定义动画函数,每帧更新进度值并重新绘制进度条
    function animate () {

      if (progressValue >= progressEnd) {
        return
      }
      // 更新进度值
      progressValue += 0.03;
      if (progressValue > progressEnd) {
        progress = progressEnd
      }

      // 渐变颜色
      ctx.beginPath();
      let grd;
      if (arg.LinearGradient && Object.keys(arg.LinearGradient).length != 0) {
        grd = ctx.createLinearGradient(0, 0, canvas.width, canvas.height)
        grd.addColorStop(0, arg.LinearGradient.start || arg.LinearGradient.stop)
        grd.addColorStop(1, arg.LinearGradient.stop || arg.LinearGradient.start)
      } else {
        grd = arg.foregroundColor || '#1B64ED'
      }
      ctx.fillStyle = grd
      ctx.moveTo(cx, cy)
      // 2.3满格
      ctx.arc(cx, cy, circleSize, initialProgress * Math.PI, progressValue * Math.PI);
      ctx.fill()
      //  检查是否达到最大帧数,如果没有则继续动画,否则停止动画
      requestAnimationFrame(animate);
      step = 0
      // 剪切圆
      clearArc(cx, cy, circle_center)
    }

在这里插入图片描述
4.中心剪切圆,因为没有直接剪切掉圆形的方法,所以使用很多个矩形堆叠成圆形的方法进行剪切,剪切完成之后添加中间的文字。

    function clearArc (x, y, radius) {
      let arcWidth = radius - step
      // Math.sqrt 求平方根
      let arcHeight = Math.sqrt(radius * radius - arcWidth * arcWidth)

      let posX = x - arcWidth
      let posY = y - arcHeight

      let widthX = 2 * arcWidth
      let heightY = 2 * arcHeight
      if (step <= radius) {
        ctx.clearRect(posX, posY, widthX, heightY)
        step += 0.8
        clearArc(x, y, radius)
      } else {
        // 文字
        if (arg.fontShow) {
          ctx.font = `${arg.fontSize || 16}px ${arg.fontFamily}`;
          ctx.fillStyle = arg.fontColor || '#000'
          ctx.textAlign = 'center';
          ctx.textBaseline = 'middle'
          ctx.fillText(inputData + '%', cx, cy);
        }

      }
    }

在这里插入图片描述

完整的代码如下:

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

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>环形进度条</title>
</head>
<style>
  @font-face {
    font-family: ledFont;
    src: url('./font/DS-DIGI.TTF'); /*自定义字体*/
  }
</style>

<body>
  <div style="display: flex">
    <div id="myCanvas" style="width:100px;height:100px"></div>
  </div>


</body>
<script>
  initProgress({
    container: 'myCanvas', // 容器
    backgroundColor: '#ddd', // 进度条背景颜色
    foregroundColor: '#000', // 进度条前景色
    LinearGradient: {  // 如果前景色需要设置渐变
      start: '#1B64ED',
      stop: '#04E8F6'
    },
    progressSize: 22, // 进度条宽度
    data: 83.33,
    fontShow: true,
    fontSize: 40,
    fontFamily: 'ledFont',
    fontColor: '#16B7C8'
  })

  function initProgress (arg) {
    //  获取 canvas 元素和 2D 绘图上下文对象
    const container = document.getElementById(arg.container);
    if (!container) {
      // 容器不存在
      throw new Error('容器不存在')
    }
    const canvas = document.createElement('canvas')
    canvas.style = `width:${container.clientWidth}px; height:${container.clientHeight}px;`;
    const ctx = canvas.getContext('2d');
    // 画布是容器的两倍不容易出现图像边缘不清晰
    canvas.width = container.clientWidth * 2
    canvas.height = container.clientHeight * 2
    container.append(canvas)
    //  设置进度条背景颜色和前景颜色
    // 进度条宽度
    const progressSize = arg.progressSize || 23
    // 圆大小
    const circleSize = canvas.width / 2
    // 中间圆大小
    const circle_center = circleSize - progressSize
    // 圆xy坐标
    const cx = canvas.width / 2
    const cy = canvas.height / 2

    // 剪切同心圆需要的
    let step = 0.8

    // 圆的初始角度和结束时的角度
    let inputData = typeof +arg.data === 'number' && !isNaN(+arg.data) ? arg.data : 0
    // 进度条从0-100的角度是从0.7-2.3
    const initialProgress = 0.7
    const totalFrames = 2.3;
    let progressValue = initialProgress

    // 根据传进来的数值,计算目前的角度
    let angle = (totalFrames - initialProgress) / 100
    let progressEnd = initialProgress + angle * inputData;

    // 绘制进度条背景
    ctx.beginPath();
    ctx.moveTo(cx, cy)
    ctx.fillStyle = arg.backgroundColor;
    ctx.arc(cx, cy, circleSize, initialProgress * Math.PI, totalFrames * Math.PI)
    ctx.fill()


    // 切掉圆心 圆心(x,y),半径radius
 
    function clearArc (x, y, radius) {
      let arcWidth = radius - step
      // Math.sqrt 求平方根
      let arcHeight = Math.sqrt(radius * radius - arcWidth * arcWidth)

      let posX = x - arcWidth
      let posY = y - arcHeight

      let widthX = 2 * arcWidth
      let heightY = 2 * arcHeight
      if (step <= radius) {
        ctx.clearRect(posX, posY, widthX, heightY)
        step += 0.8
        clearArc(x, y, radius)
      } else {
        // 文字
        if (arg.fontShow) {
          ctx.font = `${arg.fontSize || 16}px ${arg.fontFamily}`;
          ctx.fillStyle = arg.fontColor || '#000'
          ctx.textAlign = 'center';
          ctx.textBaseline = 'middle'
          ctx.fillText(inputData + '%', cx, cy);
        }

      }
    }



    // 定义动画函数,每帧更新进度值并重新绘制进度条
    function animate () {

      if (progressValue >= progressEnd) {
        return
      }
      // 更新进度值
      progressValue += 0.03;
      if (progressValue > progressEnd) {
        progressValue= progressEnd
      }

      // 渐变颜色
      ctx.beginPath();
      let grd;
      if (arg.LinearGradient && Object.keys(arg.LinearGradient).length != 0) {
        grd = ctx.createLinearGradient(0, 0, canvas.width, canvas.height)
        grd.addColorStop(0, arg.LinearGradient.start || arg.LinearGradient.stop)
        grd.addColorStop(1, arg.LinearGradient.stop || arg.LinearGradient.start)
      } else {
        grd = arg.foregroundColor || '#1B64ED'
      }
      ctx.fillStyle = grd
      ctx.moveTo(cx, cy)
      // 2.3满格
      ctx.arc(cx, cy, circleSize, initialProgress * Math.PI, progressValue * Math.PI);
      ctx.fill()
      //  检查是否达到最大帧数,如果没有则继续动画,否则停止动画
      requestAnimationFrame(animate);
      step = 0
      clearArc(cx, cy, circle_center)

    }

    // 开始动画
    animate();


  }

</script>

</html>
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值