实现效果
数字从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
。
参考
感谢阅读,如有错误,欢迎指出~