由于一些移动端兼容的原因,我们的一个项目需要前端将pdf转成可以在移动端页面直接查看的界面。为了方便解决,我们使用了pdf.js插件,可以将pdf转成canvas绘制到页面上。但是在测试过程中,发现在手机浏览器上,绘制的内容非常模糊(如下图)。经过分析,发现是手机端的高清屏导致的。解决问题后,用文字描述原因和研究结果。在说明问题之前,首先需要了解一些移动显示和cavans的小知识,方便以后的探索。如果你想直接看到结果,你可以滚动到最后。关于屏幕物理像素(DP)的一些基础知识物理像素也叫设备像素,我们经常听到手机的分辨率就是物理像素,比如iPhone7的物理分辨率是750*1334。屏幕是由pixels,即屏幕水平方向有750个像素点,垂直方向有1334个像素点。与设备无关的像素(DIP)也称为逻辑像素。比如Iphone4和Iphone3GS的尺寸都是3.5Inch,iphone4的物理分辨率是640x980,而3gs只有320x480,如果我们按照真实的布局绘制宽度为320px的图片,只有一半iphone4有内容,剩下一半是空白,为了避免这种问题,我们引入了逻辑像素,将两部手机的逻辑像素都设置为320px,方便在设备上方绘制设备无关像素像素比(DPR)。毕竟为了计算方便,我们统一了设备的逻辑像素,但是每个逻辑像素代表的物理像素是不确定的。为了确定未缩放条件下物理像素和逻辑像素之间的关系,我们引入了设备像素比(DPR)的概念设备像素比=设备像素/逻辑像素DPR=DP/DIP上面有很多理论,并附上下图进行说明。从上图可以看出,在逻辑像素大小相同的情况下,高清屏拥有更多的物理像素。普通屏下,1个逻辑像素对应1个物理像素,而在dpr=2的高清屏下,1个逻辑像素由4个物理像素组成。这就是为什么高清屏幕更细腻的原因。关于canvas的一些基础知识。Canvas绘制位图。这是每个了解canvas的人都应该知道的一个知识点,也是我们接下来要分析的问题的核心。关于位图的解释放在后面,现在我们只需要知道画布绘制的图像是位图即可。画布的宽高属性画布的宽高属性对于初学者来说是非常容易搞错的。这两个属性经常与CSS中的width和height属性混淆。比如我们有如下代码(1):样式中的宽和高分别代表界面中的canvas元素上面所占的宽高,即style的属性中的宽高代表的是画布实际像素的宽高。如果还是看不懂,可以想象下面的代码(2):canvas默认的宽高是300*150,在上面设置css后,canvas会按照设置的css宽高进行缩放(注意不是裁剪),也就是与img标签相同。上面的代码(1)其实可以用更通俗的理解,即1个逻辑像素其实是由2个画布像素填充组成的。模糊成因初探以上是对所需要的基础知识的简单介绍,下面开始正式探索。首先我们提到使用canvas绘制图像是位图,而我们平时使用的jpg、png也是位图。那么什么是位图呢?位图也称为像素图或栅格图,它通过记录图像中每个点的颜色、深度等信息来存储和显示图像。具体来说,位图可以想象成一个巨大的拼图,有无数块,每块代表一个纯色的像素。理论上,一个位图像素对应一个物理像素。但是如果你使用高清屏幕,比如苹果的视网膜屏幕,来查看图片呢?假设我们有如下代码,将显示在iphone4的retina屏幕上:iphone4本身是640980,设备的独立像素是320480,也就是说1个css像素其实是由4个物理像素组成的,canvas的像素是320150,它的css像素是320150,也就是说1个css像素会由1个canvas元素组成,所以进行转换。在视网膜屏幕下,1个画布像素(或1个位图像素)将填充4个物理像素。由于单个位图像素无法进一步划分,所以只能挑选最近的颜色,导致图片模糊。如果还有疑问,下图可以说明位图在retina屏下是如何填充的:上图左边是正常屏下的显示规律,可以看到有4个位图像素,右侧高清屏下方有16个像素点。由于无法切割像素,颜色发生了变化。但是还有一点没有解释清楚,就是为什么图片会直接取最近的颜色而不是原来的值,这也是blur背后的黑手。幕后黑手---平滑技术下面是我一个同学给我讲解的。刚才我们说了每个位图元素其实就是一个纯色像素。现在假设我们需要在普通屏幕上绘制一个数字“0”,css大小为4px*4px,dpr为1,那么我们绘制的内容应该如下图所示,其中1代表黑色像素,0代表白色像素.可以看到,当dpr比较小的时候,我们的“0”模式还是比较明显的。现在如果我们改变css的大小,在retina屏幕下绘制图片,会是什么效果呢?我们知道在视网膜屏幕下,一个css像素代表4个物理像素。如果我们不做任何处理,直接按照上面的方式排列矩阵,展开矩阵,我们会发现在retina屏幕下,我们的pattern是非常参差不齐的。该图像明显缺少一丝色相。如果我们对图片稍作改动,改成下图,感觉图片瞬间柔和了,但是本来应该填4个0的地方变成了3个1加1个0。这其实就是所谓的图像平滑处理。为了解决锯齿感,改变了原来的颜色。在填充较多颜色的图片上,为了更加自然,图片的连接处变成了近似色,这也解释了为什么在填充上面的颜色时,使用近似色而不是原来的颜色。原因总结经过上面的解释,现在我们来总结一下结论。现在移动端普及,高清屏基本普及,1px的css像素点其实代表了4个或者更多的物理像素点。但是由于我们的代码问题,我们的1pxcsspixel和1canvaspixel是等号,导致1canvaspixel需要填充4个或者更多的物理像素。为了保证平滑的图像处理,填充剩余的物理像素是原始颜色的近似值,从而导致图像模糊。解决思路了解了问题的原因之后,解决问题就非常容易了。解决问题最重要的是让一个画布像素和一个物理像素等号。高版本浏览器的window对象有一个devicePixelRatio属性,这个属性就是上面说的dpr,我们可以在canvas元素css的宽高确定后做这个letcanvas=document.getElementById('canvas');让ctx=canvas.getContext('2d');让dpr=window.devicePixelRatio;//假设dpr为2//获取css的宽高let{width:cssWidth,height:cssHeight}=canvas.getBoundingClientRect();//根据dpr,展开canvas画布的像素,使1个Canvas像素等于1个物理像素canvas.width=dpr*cssWidth;canvas.height=dpr*cssHeight;//随着画布的扩大,画布的坐标系也随之扩大。如果在原坐标系下绘制,内容会缩小//因此需要通过ctx.scale(dpr,dpr);来放大绘制比例尺经验总结很多时候,我们在发现问题的时候,不能只着眼于问题的解决,而应该深入去了解问题的原因,这样才能更好的解决问题。这继续下去。添加一名作者