使用canvas绘制环状数据图

实现效果

在这里插入图片描述

数字从0开始变化,且颜色也跟着变化。

开发过程

这里采用的是vue,这个圆环是一个组件。

<div class="production-statsistic-cicle">
    <canvas :id="id" width="75" height="75" />
</div>
// 支持传入的参数
@Prop() private id!: string;
@Prop({ default: 75 }) private percent!: number;
@Prop({ default: '单量占比' }) private title!: number;
// 在生命周期Mounted中我们调用draw 函数
privated mounted(){
	this.draw();
}

draw函数中,我们需要做这么几件事:

  • 获得当前的canvas,并记录它的上下文;
  • 为了后期方便,也记录canvas的宽高;
  • 先绘制灰色的圆环;
  • 再绘制高亮的地方;
  • 绘制环中的标题;
  • 最后绘制环中的数据。
private draw(){
    let canvas: any = document.getElementById(this.id);
    this.ctx = canvas.getContext('2d');
    this.width = canvas.width;
    this.height = canvas.height;
    this.drawBg();
    this.drawHeighLight(this.percent);
    this.drawTitle();
    this.drawNum(this.percent);
 }

绘制背景:

// 绘制背景
  private drawBg(){
    this.ctx.beginPath();
    this.ctx.lineWidth = 6;
    var radius = (this.width / 2) - this.ctx.lineWidth;
    this.ctx.lineCap = "round";
    this.ctx.strokeStyle = "#E5E6EA";
    this.ctx.arc(this.width / 2, this.height / 2, radius, 0, Math.PI * 2, false);
    this.ctx.stroke();
    this.ctx.closePath();
  }

绘制高亮部分:

// 获得颜色
// 含50%~100%为绿色
// 含30%~50%为蓝色
// 含10%~30%为橙色
// 含0%~10%为红色
private getColor(n: number){
    if((+n) < 10) return '#FE5A59';
    else if((+n) >= 10 && (+n) < 30) return '#FF723C';
    else if((+n) >= 30 && (+n) < 50) return '#2971FF';
    else return '#09B49C'; 
}

// 绘制高亮区域
private drawHeighLight(n: number){
    this.ctx.beginPath();
    this.ctx.lineWidth = 6;
    var radius = (this.width / 2) - this.ctx.lineWidth;
    this.ctx.lineCap = "round";
    this.ctx.strokeStyle = this.getColor(n);
    var rad = Math.PI * 2 / 100;
    this.ctx.arc(this.width / 2, this.height / 2, radius, -Math.PI/2, -Math.PI/2 + (+n) * rad, false);
    this.ctx.stroke();
    this.ctx.closePath();
}

绘制标题:

private drawTitle(){
    this.ctx.fillStyle = '#ABACB4';
    this.ctx.font = 12 + "px Helvetica";
    var textWidth = this.ctx.measureText(this.title).width;
    this.centerX = this.width / 2;
    this.centerY = this.height / 2;
    this.ctx.fillText(this.title, this.centerX - (textWidth / 2), this.centerY - 4);
}

绘制数据:

private drawNum(num: number){
    this.ctx.fillStyle = '#0E0F37';
    // 这里的字体是因为如果使用默认的话会有点糊
    this.ctx.font = 12 + "px Helvetica";
    const text = `${num}%`;
    const textWidth = Math.floor(this.ctx.measureText(text).width);
    this.centerX = this.width / 2;
    this.centerY = this.height / 2;
    this.ctx.fillText(text, this.centerX - (textWidth / 2) + 2, this.centerY + 14);
}

优化

想让它动起来,需要使用定时器,第一个反应想到的是requestAnimationFrame,对比setTimeout等定时器,有两个优点:

  • 会把每一帧中的所有DOM操作集中起来,在一次重绘或者回流中完成,刷新频率为每秒60帧;
  • 在隐藏或者不可见的元素中,requestAnimationFrame不会对它们进行处理,意味着更少的cpu等使用。

在每一次更新数据的时候,我们需要清空画布,不然就会出现文字叠加的问题,和高亮部分模糊的问题,封装一个step函数,将上面这些绘图函数封装起来:

private step(){
    if(this.speed < this.percent){
      this.speed++;
      this.num = this.speed === this.percent ? this.percent : this.num + 1;
      this.ctx.clearRect(0, 0, 150, 150);
      this.drawBg();
      this.drawHeightLight(this.speed);
      this.drawNum(this.num);
      this.drawTitle();
      this.circleAnimation = requestAnimationFrame(this.step);
    }
}

修改draw函数:

private draw(){
    let canvas: any = document.getElementById(this.id);
    this.ctx = canvas.getContext('2d');
    this.width = canvas.width;
    this.height = canvas.height;
    this.step();
 }

最后别忘了清除requestAnimationFrame:

beforeDestory(){
    window.cancelAnimationFrame(this.circleAnimation);
}

再次优化

在电脑上比较清晰,在手机上有点模糊,于是得优化,最后优化后边框少了锯齿的感觉。在这里插入图片描述
解决方法参考了 解决 canvas 在高清屏中绘制模糊的问题

假设 devicePixelRatio 的值为 2 ,一张 100×100 像素大小的图片,在 Retina 屏幕下,会用 2 个像素点的宽度去渲染图片的 1 个像素点,因此该图片在 Retina 屏幕上实际会占据 200×200 像素的空间,但是我们总的像素点只有100×100,这就相当于图片被放大了一倍,因此图片会变得模糊。
类似的,在 canvas context 中也存在一个 backingStorePixelRatio 的属性,该属性的值决定了浏览器在渲染canvas之前会用几个像素来来存储画布信息。 backingStorePixelRatio 属性在各浏览器厂商的获取方式不一样,所以需要加上浏览器前缀来实现兼容。
修改canvas:

<canvas :id="id" width="75" height="75" ></canvas>

我们要绘制一个width: 75;height:75的图形,那么在绘制的时候canvas的宽高是width:75px;height:75px,但是画布的宽高应该为当前宽高乘以分辨率比。这样就能解决了。在init方法中修改宽高:

private init(){
    let canvas: any = document.getElementById(this.id);
    this.ctx = canvas.getContext('2d');
    this.ratio = this.getPixelRatio(this.ctx);
    canvas.style.width = canvas.width + 'px';
    canvas.style.height = canvas.height + 'px';
    canvas.width = canvas.width * this.ratio;
    canvas.height = canvas.height * this.ratio;
    this.width = canvas.width;
    this.height = canvas.height;
    this.step();
  }

与之对应的是将背景边框、高亮边框 * this.ratio

参考

解决 canvas 在高清屏中绘制模糊的问题


感谢阅读,如有错误,欢迎指出~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值