本文将对 Canvas 在高清屏中绘制出现模糊问题进行分析,并给出解决方案 🎏
问题剖析
文章:高清屏一像素边框问题 中对物理像素(physical pixel) 、CSS 像素和 devicePixelRatio 等概念进行了介绍
在高清屏中,Canvas 绘制时会出现模糊的问题,在文章 High DPI Canvas 中对此进行了分析介绍。其中涉及两个概念:webkitBackingStorePixelRatio 和 devicePixelRatio
浏览器绘制 Canvas 渲染到屏幕中分两个过程:
- 绘制过程:webkitBackingStorePixelRatio
webkitBackingStorePixelRatio表示浏览器在绘制 Canvas 到缓存区时的绘制比例,若图片宽高为200px,webkitBackingStorePixelRatio为 2,那么 Canvas 绘制这个图片到缓存区时,宽高就为400px - 渲染过程:devicePixelRatio
Canvas 显示到屏幕中还需要渲染过程,渲染过程根据devicePixelRatio参数将缓存区中的 Canvas 进行缩放渲染到屏幕中
分析图片在高清屏中 Canvas 绘制会模糊的原因:
1、devicePixelRatio = device pixel / CSS pixel
如果 devicePixelRatio = 2 那么对于 200px * 200px 的图片要绘制到屏幕中,那么对应的屏幕像素(物理像素) 就是 400px * 400px
2、在大部分高清屏中,例如 Macbook Pro 中
webkitBackingStorePixelRatio = 1
devicePixelRatio = 2
将一个 200px * 200px 的图片 Cavnas 绘制到该屏幕中的流程:
webkitBackingStorePixelRatio = 1
绘制到缓存区的大小也为:200px * 200pxdevicePixelRatio = 2
200px * 200px的图片对应到屏幕像素为400px * 400px,devicePixelRatio = 2浏览器就把缓存区的200px * 200px宽高分别放大两倍渲染到屏幕中,所以就导致模糊
解决方案
-
将 Canvas 宽高进行放大,放大比例为:
devicePixelRatio / webkitBackingStorePixelRatioconst devicePixelRatio = window.devicePixelRatio || 1 const backingStoreRatio = context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1 const ratio = devicePixelRatio / backingStoreRatio -
通过 CSS 设置将 Canvas 缩小为原大小
const oldWidth = canvas.width;const oldHeight = canvas.height;canvas.width = oldWidth * ratio;canvas.height = oldHeight * ratio;canvas.style.width = oldWidth + 'px';canvas.style.height = oldHeight + 'px';
举例分析一下 Canvas 绘制图片,文字的适配方案:
1、对于图片绘制 drawImage 方法:
由于 Canvas 放大后,相应的绘制图片时也要放大,有两种方式:
-
drawImage目标宽高分别乘以ratiocontext.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 的缩放变换。推荐使用这种方式 👍
2、对于文字绘制 fillText 方法
由于 Canvas 放大了,绘制文字时,字体也要放大,绘制完成后,字体再缩小回原大小
context.font = context.font.replace( /(\d+)(px|em|rem|pt)/g, function(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; });
其他
1️⃣ webkitBackingStorePixelRatio 在 Chrome 中已经废弃,详细参考:bugs.chromium.org/p/chromium/…
2️⃣ 现在高清屏中 Canvas 绘制图片 drawImage,不需要经过如上处理也不会出现模糊的情况了(只在 Mackbook Pro, iPhone 6S 上分别测试过),这点在网上并没有找到更多的信息
但是 Canvas 的其他绘制方法例如绘制文字 fillText 不经过处理高清屏中绘制仍然会模糊,所以还是需要这个polyfill:YingshanDeng/hidpi-canvas-polyfill