canvas详解(3)-模糊问题
1.模糊的原因
canvas
绘图理论上应该是清晰的,但是有些屏幕上看到的确是模糊的,究其原因有3点:
css样式影响
canvas
绘图是基于自身的宽高设置,必须明确指定宽高,不能使用百分比。如果出现线条锯齿状原因,一般都是因为,canvas
的css
样式放大了原本的canvas
图形导致的。例如:
<canvas id="test" width="200" height="100" style={{width:400,height:200}}></canvas>
css将canvas自身放大了一倍,导致图像不清晰
设备像素比
不同的设备,其物理像素/屏幕宽度是不一样,从而就导致了相同的屏幕宽度下,1px的物理像素有可能不再是1px。而设备像素比的不同是导致图像模糊的主要原因。
为了解决这一问题,我们需要完成如下步骤:
- 获取设备像素比;
- 将canvas宽高和图形放大相同的像素比倍数。(即所有的canvas图形都放大设备像素比倍数,包括文字);
- 通过css将canvas缩小对应的倍数。
将图形整体放大是为了适应当前实际物理像素,这样绘制的图形才是最清晰的,但是因为放大后的图形不是我们想要的,所以必须用css缩小到相应的倍数,才能呈现出正常的图形。
需注意:canvas的scale只是对内部图形进行放大缩小,并不能改变画布大小。即是说放大后部分图形将可能不能显示,因为超出了画布范围。
线条绘制原理
canvas
的线条绘制是以当前线条坐标为中心,左右各绘制指定lineWidth
一半的宽度(lineWidth
默认是1px),而由于canvas无法在整个像素宽内只绘制半个像素,所以实际默认绘制的线条是2px的图形,只是因为默认被填充的半像素导致整体视觉颜色变浅了。
例如:
ctx.lineWidth = 1;
ctx.moveTo(50,50)
ctx.lineTo(150,50);
ctx.stroke();
绘制该线条的时候,canvas会在y轴为50的线上下各绘制0.5px,所以需要绘制的区域就是[49.5~50,50~50.5]这2个区域都是半像素区域canvas无法绘制,所以实际绘制的区域变成了[49~50,50~50],因为[49~49.5,50.5~51]是被额外填充区域,所以整体颜色变淡了。
既然找到了问题的原因,那么解决办法就相对好找了,我们只需让绘制的上下2部分都变成整像素的就OK了。
ctx.lineWidth = 1;
ctx.moveTo(50,50.5)
ctx.lineTo(150,50.5);
ctx.stroke();
我们将y轴增加0.5,那么绘制的时候就是以50.5轴线上下绘制0.5px,实际绘制区域变成了[50~50.5,50.5~51]
而50~51正好构成了一整个像素,canvas能够直接绘制,所以绘制的效果就是1px清晰效果。
当然绘制整数倍宽度的线条一般不会出现这种现象
ctx.lineWidth = 2;
ctx.moveTo(50,50)
ctx.lineTo(150,50);
ctx.stroke();
绘制结果为2px清晰线条
2.模糊解决方案
获取设备像素比
//设备像素比
getPixelRatio(context) {
var backingStore = context.backingStorePixelRatio ||
context.webkitBackingStorePixelRatio ||
context.mozBackingStorePixelRatio ||
context.msBackingStorePixelRatio ||
context.oBackingStorePixelRatio ||
context.backingStorePixelRatio || 1;
return (window.devicePixelRatio || 1) / backingStore;
};
放大画布
const ctx = canvas.getContext('2d');
let ratio = this.getPixelRatio(ctx);
let scale = ratio;
this.setState({ ratio: 1 / ratio });//缓存缩小比例
当然这里还可以根据屏幕大小设置画布的缩放比例,但是无论画布怎么缩放,都必须让画布放大到屏幕的真实像素比,所有的图形、文字都应放大。
canvas提供了scale()方法用于缩放画布图形,但是该方法只是将画布中的图形在当前画布大小下缩放,实际画布还是原来的大小,只是内部图形被缩放了,这根本不能满足我们的业务场景,我们希望的效果是画布和其内部的图形等比缩放。所以我们只能在当前画布宽高下,对所有的图形进行等比重绘,这样就能达到想要的效果了。
将画布缩小到实际大小
因为我们按设备像素比将整个画布放大了,那么我们就需要通过css来还原它的显示大小。通过zoom属性,缩放整个画布。
<canvas id={this.props.canvasId} width={this.props.width} height={this.props.height}
style={{ zoom: this.state.ratio}}></canvas>
style中的内容才是重点。网上有很多说在父级进行缩放纯属扯淡。只能在canvas自身上缩放。
当然不用zoom,直接使用css定义width,height为对应比例的宽高效果也是一样的。
例如:
<canvas id={this.props.canvasId} width={this.props.width} height={this.props.height}
style={{ width: this.props.width*this.state.ratio,
height:this.props.height*this.state.ratio}}></canvas>
完整代码请参考:canvas详解(1)-原理。