canvas 显示模糊问题

2,051 阅读2分钟

引子

近期的工作中,是继 canvas 宽高问题 之后碰到的第二个问题。

显示模糊问题

在 PC 浏览器上显示时,没有发现明显的模糊,还可以接受。但在手机上就会有明显的模糊。这是示例,扫描访问二维码如下。

18-qrcode-canvas-image

示例中,用 css 控制 canvas 的宽高,里面的图片展示效果不一致。查询资料,在 stackoverflow 中发现同样的问题,通过实际测试发现:

  • canvas 元素自身的属性 widthheight,决定了多少像素可以显示在画布上,如果不设置,width 默认值是 300,height 默认值是 150。
  • css 的属性 widthheight,是指在屏幕上元素显示的大小,如果没有对 canvas 进行 css 设置,则会采用 canvas 的默认大小。
  • 如果设置了 css 属性 widthheight,当在画布里面使用 drawImage 设置图片宽高时,显示的宽高值会根据一定比例进行转换。例如在上面例子中,设置的图片是宽 300 高 90, canvas 默认宽高渲染像素 300 和 150,(css 高度/canvas 自身属性高度) * drawImage 设置的高度 = (90/150)* 90 = 54。

规范里面还真没看出来这些。

原因

在 stackoverflow 上也找到相关的问题,在回答中有相关介绍的文章HTML5 Rocks。原因是 canvas 绘制时独立于设备像素比(devicePixelRatio)。受到 devicePixelRatio 影响,在高清显示屏上,一个逻辑像素对应多个实际的设备物理像素。例如在 devicePixelRatio 为 2 的设备上,css 设置的 100px,意味着设备上要填充 200px 物理像素,那么当 canvas 绘制 100px 的区域时,实际是想在设备填充 100px 物理像素,但由于 devicePixelRatio 的作用,设备要求显示 200px 的物理像素,浏览器就智能的填充了像素之间的空格,以便以适当的大小显示元素。

在 Safari6 中支持一个属性 backingStorePixelRatio,该属性决定了浏览器在渲染 canvas 会用几个像素来绘制画布的信息。有些类似于 devicePixelRatio。Safari6 的值为 2,所以在 Safari6 中 canvas 不会模糊,但后来去掉了,现在浏览器不支持这个属性,具体见 Issue 277205

解决方法

解决的思路就是通过检测设备像素比,绘制对应倍数比例的 canvas 元素。方法如下:

function createHDCanvas (w=300,h=150) {
  var ratio = window.devicePixelRatio || 1;
  var canvas = document.createElement('canvas');
  canvas.width = w * ratio; // 实际渲染像素
  canvas.height = h * ratio; // 实际渲染像素
  canvas.style.width = `${w}px`; // 控制显示大小
  canvas.style.height = `${h}px`; // 控制显示大小
  canvas.getContext('2d').setTransform(ratio, 0, 0, ratio, 0, 0);
  return canvas;
}

这是对比示例,扫描访问二维码如下。

18-qrcode-canvas-image-hd

参考资料