使用canvas绘制圆环进度条

技术要求

需要一点点数学基础
需要对 canvas 的常见的方法熟悉

一点点数学基础

已知圆心,半径,角度,求圆上的点坐标

canvas 常见的方法

菜鸟教程

扬帆起航

  1. 首先创建一个canvas ,并将这个dom添加在html中

方法名称:createCanvas

// 需要申明两个全局变量
let isIconLoadSuccess = false;
let iconLoading = null;

// 以下是一个方法
const canvas = document.createElement('canvas');
canvas.width = 300;
canvas.height = 300;
canvas.style.border = '1px #ccc solid';

// 提前加载图标数据
const imgObj = new Image();
imgObj.src = './success-filling.png';
imgObj.onload = function () {
  isIconLoadSuccess = true;
  iconLoading = this;
};

return canvas;
  1. 定义一个绘制圆环的方法

方法名称:drawCircle(ctx, config)

绘制圆环的思路
使用arc绘制一个圆形,在其内部填充一个样式,将圆的边框lineWidth设置的宽度大一点,这个宽度就是圆环的大小,设置strokeStyle的颜色,该颜色就是圆环的颜色,最后设置连接处样式lineCap,这里建议将lineCap设置成round 因为最终想要的是圆环,所以角度应该是(0,360) 最后调用strokeclosePath上色和关闭路径

const {
   x, y, radius, startAngle, endAngle, color, lineWidth,
 } = config;
 ctx.beginPath();
 ctx.arc(x, y, radius, startAngle, endAngle, false);
 // 设定曲线粗细度
 ctx.lineWidth = lineWidth;
 // 给曲线着色
 ctx.strokeStyle = color;
 // 连接处样式
 ctx.lineCap = 'round';
 // 给环着色
 ctx.stroke();
 ctx.closePath();
  1. 绘制圆环

方法名称:circle(percent = '0.0')

const { width, height } = canvas;
const ctx = canvas.getContext('2d');
// 清除画布
ctx.clearRect(0, 0, width, height);
// 保存
ctx.save();
/* 填充文字 */
ctx.font = '24px Microsoft YaHei';
/* 文字颜色 */
ctx.fillStyle = '#999';
/* 文字内容 */
const insertContent = '本月任务进度';
// 拿到文本内容的像素相关信息 单位长度(px)
const measureText = ctx.measureText(insertContent);
/* 插入文字,后面两个参数为文字在画布中的坐标点 */
/* 此处注意:text.width获得文字的宽度,然后就能计算出文字居中需要的x值 */
ctx.fillText(insertContent, (width - measureText.width) / 2, (height / 2) + 45);

/* 填充百分比 */
ctx.font = '60px Microsoft YaHei';
ctx.fillStyle = '#222';

const ratioStr = `${(parseFloat(percent) * 100).toFixed(0)} %`;
const text = ctx.measureText(ratioStr);
ctx.fillText(ratioStr, (width - text.width) / 2, (height / 2) + 10);

/* 开始圆环 */
const circleConfig = {
  /* 圆心坐标 */
  x: width / 2,
  y: height / 2,
  /* 半径,下方出现的150都是半径 */
  radius: (width / 2) - 30,
  /* 环的宽度 */
  lineWidth: 24,
  /* 开始的度数-从上一个结束的位置开始 */
  startAngle: 0, // 注意这里的0是3点钟方向,而非12点方向,和数学里的不一样
  /* 结束的度数 */
  endAngle: 360,
  color: '#E7EFF4',
};

/* 灰色的圆环 */
drawCircle(ctx, circleConfig);

/* 有色的圆环 */
const holeCicle = 2 * Math.PI;
const angle = percent * 360; // 圆弧的角度

// 圆心坐标:(x0, y0)
// 半径:r
// 弧度:a   => 圆弧计算公式:(角度/180)*Math/.PI
// 则圆上任一点为:(x1, y1)
// x1 = x0 + r * cos(a)
// y1 = y0 + r * sin(a)
const x1 = circleConfig.x + circleConfig.radius * Math.cos(((angle - 90) / 180) * Math.PI) - 25;
const y1 = circleConfig.y + circleConfig.radius * Math.sin(((angle - 90) / 180) * Math.PI) - 25;

// 处理渐变色
const gnt1 = ctx.createLinearGradient(circleConfig.radius * 2, circleConfig.radius * 2, 0, 0);
gnt1.addColorStop(0, '#FF8941');
gnt1.addColorStop(0.3, '#FF8935');
gnt1.addColorStop(1, '#FFC255');

drawCircle(ctx, {
  ...circleConfig,
  /* 从-90度的地方开始画 */
  startAngle: -0.5 * Math.PI, // 把起始点改成数学里的12点方向
  endAngle: -0.5 * Math.PI + percent * holeCicle,
  color: gnt1,
});

// 填充小图标
if (isIconLoadSuccess) {
  // 这里的this指的是imgObj,第二三个参数是它的坐标,四五个是长款
  ctx.drawImage(iconLoading, x1, y1, 50, 50);
}
  1. 来一点动画

方法名称:drawFrame(percent, callback)

// 在该方法之外声明一个全局变量
let speed = 0;
// 以下是 drawFrame 方法体
const id = window.requestAnimationFrame(() => { drawFrame(percent, callback); });
circle(speed.toString());
if (speed >= percent) {
  window.cancelAnimationFrame(id);
  speed = 0;
  if (callback) {
    callback();
  }
  return;
}
speed += 0.01;
  1. 再来一个快照功能

方法名称:createImage(src)

const image = new Image();
image.src = src;
return image;
  1. 调用声明好的方法
const app = document.getElementById('app');
const canvas = createCanvas();

let i = 0;
setInterval(() => {
  if (i > 1) {
    i = 0;
  }
  i = Math.random();
  drawFrame(i.toString(), () => {
    const image = createImage(canvas.toDataURL());
    app.appendChild(image);
    window.scrollTo(0, document.body.scrollHeight);
  });
}, 3000);

app.appendChild(canvas);

完整代码

<!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>
  <style>
    body {
      width: 100%;
      height: 100vh;
      overflow-y: auto;
      overflow-x: hidden;
    }
  </style>
</head>

<body>
  <div id="app"></div>
</body>

<script>
  // 图标是否加载成功
  let isIconLoadSuccess = false;
  let iconLoading = null;
  function createCanvas() {
    const canvas = document.createElement('canvas');
    canvas.width = 300;
    canvas.height = 300;
    canvas.style.border = '1px #ccc solid';

    // 提前加载图标数据
    const imgObj = new Image();
    imgObj.src = './success-filling.png';
    imgObj.onload = function () {
      isIconLoadSuccess = true;
      iconLoading = this;
    };

    return canvas;
  }

  const app = document.getElementById('app');
  const canvas = createCanvas();

  /* 画曲线 */
  function drawCircle(ctx, config) {
    const {
      x, y, radius, startAngle, endAngle, color, lineWidth,
    } = config;
    ctx.beginPath();
    ctx.arc(x, y, radius, startAngle, endAngle, false);
    // 设定曲线粗细度
    ctx.lineWidth = lineWidth;
    // 给曲线着色
    ctx.strokeStyle = color;
    // 连接处样式
    ctx.lineCap = 'round';
    // 给环着色
    ctx.stroke();
    ctx.closePath();
  }

  function circle(percent = '0.0') {
    const { width, height } = canvas;
    const ctx = canvas.getContext('2d');
    // 清除画布
    ctx.clearRect(0, 0, width, height);
    // 保存
    ctx.save();
    /* 填充文字 */
    ctx.font = '24px Microsoft YaHei';
    /* 文字颜色 */
    ctx.fillStyle = '#999';
    /* 文字内容 */
    const insertContent = '本月任务进度';
    // 拿到文本内容的像素相关信息 单位长度(px)
    const measureText = ctx.measureText(insertContent);
    /* 插入文字,后面两个参数为文字在画布中的坐标点 */
    /* 此处注意:text.width获得文字的宽度,然后就能计算出文字居中需要的x值 */
    ctx.fillText(insertContent, (width - measureText.width) / 2, (height / 2) + 45);

    /* 填充百分比 */
    ctx.font = '60px Microsoft YaHei';
    ctx.fillStyle = '#222';

    const ratioStr = `${(parseFloat(percent) * 100).toFixed(0)} %`;
    const text = ctx.measureText(ratioStr);
    ctx.fillText(ratioStr, (width - text.width) / 2, (height / 2) + 10);

    /* 开始圆环 */
    const circleConfig = {
      /* 圆心坐标 */
      x: width / 2,
      y: height / 2,
      /* 半径,下方出现的150都是半径 */
      radius: (width / 2) - 30,
      /* 环的宽度 */
      lineWidth: 24,
      /* 开始的度数-从上一个结束的位置开始 */
      startAngle: 0, // 注意这里的0是3点钟方向,而非12点方向,和数学里的不一样
      /* 结束的度数 */
      endAngle: 360,
      color: '#E7EFF4',
    };

    /* 灰色的圆环 */
    drawCircle(ctx, circleConfig);

    /* 有色的圆环 */
    const holeCicle = 2 * Math.PI;
    const angle = percent * 360; // 圆弧的角度

    // 圆心坐标:(x0, y0)
    // 半径:r
    // 弧度:a   => 圆弧计算公式:(角度/180)*Math/.PI
    // 则圆上任一点为:(x1, y1)
    // x1 = x0 + r * cos(a)
    // y1 = y0 + r * sin(a)
    const x1 = circleConfig.x + circleConfig.radius * Math.cos(((angle - 90) / 180) * Math.PI) - 25;
    const y1 = circleConfig.y + circleConfig.radius * Math.sin(((angle - 90) / 180) * Math.PI) - 25;

    // 处理渐变色
    const gnt1 = ctx.createLinearGradient(circleConfig.radius * 2, circleConfig.radius * 2, 0, 0);
    gnt1.addColorStop(0, '#FF8941');
    gnt1.addColorStop(0.3, '#FF8935');
    gnt1.addColorStop(1, '#FFC255');

    drawCircle(ctx, {
      ...circleConfig,
      /* 从-90度的地方开始画 */
      startAngle: -0.5 * Math.PI, // 把起始点改成数学里的12点方向
      endAngle: -0.5 * Math.PI + percent * holeCicle,
      color: gnt1,
    });

    // 填充小图标
    if (isIconLoadSuccess) {
      // 这里的this指的是imgObj,第二三个参数是它的坐标,四五个是长款
      ctx.drawImage(iconLoading, x1, y1, 50, 50);
    }
  }

  function createImage(src) {
    const image = new Image();
    image.src = src;
    return image;
  }

  // 动画函数
  let speed = 0;
  function drawFrame(percent, callback) {
    const id = window.requestAnimationFrame(() => { drawFrame(percent, callback); });
    circle(speed.toString());
    if (speed >= percent) {
      window.cancelAnimationFrame(id);
      speed = 0;
      if (callback) {
        callback();
      }
      return;
    }
    speed += 0.01;
  }
  
  
  let i = 0;
  setInterval(() => {
    if (i > 1) {
      i = 0;
    }
    i = Math.random();
    drawFrame(i.toString(), () => {
      const image = createImage(canvas.toDataURL());
      app.appendChild(image);
      window.scrollTo(0, document.body.scrollHeight);
    });
  }, 3000);

  app.appendChild(canvas);
</script>

</html>

使用到的小图标

ios swfit 半环形进度条 canvas环形进度条_html5

最终效果图

ios swfit 半环形进度条 canvas环形进度条_canvas_02