# 问题剖析
TIP
移动端适配方案中对物理像素(physical pixel)、CSS像素和devicePixelRatio等概念进行了介绍
在高清屏中,Canvas绘制时会出现模糊问题,在文章High DPI Canvas (opens new window)中对比进行了分析介绍。其中涉及两个概念:webkitBackingStorePixelRatio和devicePixelRatio
浏览器绘制Canvas渲染到屏幕中分两个过程
绘制过程:webkitBackingStorePixelRatio
webkitBackingStorePixelRatio表示浏览器在绘制Canvas到缓存区时的绘制比例,若图片宽高为200px,webkitbackingStorePixelRatio为2,那么Canvas绘制这个图片到缓存区时,宽高为400px;
渲染过程:devicePixelRatio
Canvas显示到屏幕中还需要渲染过程,渲染过程根据devicePixelRatio参数将缓存区中的canvas进行缩放渲染到屏幕中

分析图片在高清屏中Canvas绘制会模糊的原因:
devicePixelRatio = device pixel / CSS pixel
如果devicePixelRatio = 2 那么对于200px * 200px的图片要绘制到屏幕中,那么对应的屏幕像素(物理像素)就是400px * 400px
在大部分高清屏中,例如Macbook Pro中
webkitBackingStorePixelRatio = 1; devicePixelRatio = 2;
将一个200px * 200px 的图片Canvas绘制到该屏幕中的流程
webkitBackingStorePixelRatio = 1;
绘制到缓存区的大小也为:200px * 200px;
devicePixelRatio = 2;
200px * 200px 的图片对应到屏幕像素为400px * 400px ,devicePixelRatio = 2浏览器就把缓存区的200px * 200px 宽高分别放到两倍渲染到到屏幕中,所以导致模糊
# 解决方案
将Canvas宽高进行方法,放大比例为:devicePixelRatio / webkitBackingStorePixelRatio
var devicePixelRatio = window.devicePixelRatio || 1 var backingStoreRatio = context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1 var ratio = devicePixelRatio / backingStoreRatio通过css设置将Canva缩小为原大小
var oldWidth = canvas.width; var oldHeight = canvas.height; canvas.width = oldWidth.ratio; cvans.height = oldWidth.ratio; canvas.style.width = oldWidth + 'px' canvas.style.height = oldHeight + 'px'
举例分析一下Canvas绘制图片,文字的适配方案:
对于图片绘制drawImage方法
由于Canvas放大后,相应的绘制图片时也要放大,有两种方式
- drawImage目标宽高分别乘以ratio
context.drawImage(image, , srcx, srcy, srcw, srch, desx, desy, desw * ratio, desh * ratio)- context.scale缩放
context.scale(ratio, ratio) // 绘制图片 context.drawImage(...) context.scale(1/ratio, 1/ratio)此种方式在绘制图片之前,调用 scale 设置 Canvas 缩放变换;绘制完成后,需要重置 Canvas 的缩放变换。推荐使用这种方式 👍
对于文字绘制fillText方法
由于Canvas方法了,绘制文字时,字体也要放大,绘制完毕后,字体要缩小会原大小
context.font = context.font.replace(/(\d+)(px|em|rem|pt)/, (w, m, u) => { return (m * ratio) + u; }) // 绘制文字 context.fillText(...) context.font = context.font.replace( /(\d+)(px|em|rem|pt)/g, function(w, m, u) { return (m / ratio) + u; } );
关于 Canvas 在高清屏绘制的兼容,github 有一个 polyfill jondavidjohn/hidpi-canvas-polyfill (opens new window) 但是这仓库并没有考虑 drawImage,如果直接使用这个 polyfill,在绘制图片时,宽高都会变小;我 fork 了这个仓库,并增加了 drawImage 处理:YingshanDeng/hidpi-canvas-polyfill。更多 Canvas 绘制方法的兼容参考这个仓库的源码。
# 其他
1️⃣ webkitBackingStorePixelRatio 在 Chrome 中已经废弃,详细参考:https://bugs.chromium.org/p/chromium/issues/detail?id=277205
2️⃣ 现在高清屏中 Canvas 绘制图片 drawImage,不需要经过如上处理也不会出现模糊的情况了(只在 Mackbook Pro, iPhone 6S 上分别测试过),这点在网上并没有找到更多的信息
但是 Canvas 的其他绘制方法例如绘制文字 fillText 不经过处理高清屏中绘制仍然会模糊,所以还是需要这个polyfill:YingshanDeng/hidpi-canvas-polyfill