美文网首页JsWeb前端之路让前端飞
Canvas 绘图模糊问题解析

Canvas 绘图模糊问题解析

作者: ac68882199a1 | 来源:发表于2017-11-26 22:57 被阅读766次

    初次使用 canvas 来绘图的小伙伴可能遇到过绘制出来的图像模糊不清的情况

    我们先一起来绘制一个矩形

    <body>
    
    <style>
    canvas {background: #eee;}
    </style>
    <canvas id="canvas" width="200" height="200"></canvas>
    
    <script>
    const canvas = document.getElementById('canvas')
    const ctx = canvas.getContext('2d')
    
    ctx.strokeStyle = '#000'
    ctx.lineWidth = 1
    ctx.strokeRect(10, 10, 100, 100)
    </script>
    
    </body>
    

    效果图如下:

    我们明明设置的线条宽度是 1px,为什么会导致绘制出来的效果是 2px 甚至看上去更粗呢?

    要解决这点,我们需要先搞清楚两个概念:

    1. 设备像素比
    2. canvas 的 css 宽高与上下文宽高

    设备像素比

    这个概念相信很多小伙伴都有所了解,举个简单的栗子,在 iPhone3G 时代,屏幕宽度是 320px,其宽度上的物理像素也是 320px;而到了 4s 时代,屏幕宽度依然是 320px,但是宽度上的物理像素却变成了 640px,是宽度的两倍

    屏幕宽度没变,物理像素却增加了,所以为了屏幕显示的内容不改变,原先需要一个像素绘制的点,现在会用两个像素来绘制

    为了表示这种屏幕的特性,浏览器全局对象下就有了这样一个属性——devicePixelRatio 设备像素比,它的计算方式是 物理像素 / 屏幕宽度的像素,所以 3G 的设备像素比为 1 , 4s 为 2,而现在 iPhone 的 plus 型号手机的设备像素比为 3,甚至部分出现了比值为 4 的安卓设备

    // 获取设备像素比
    window.devicePixelRatio
    

    回到 canvas 上的问题上,当我们想要绘制一条 1px 的线时,由于当前浏览器的设备像素比是 2,所以实际上是通过 2 个像素点来绘制的,但是即便是2个像素绘制的线条也不应该出现模糊的问题,而现在确实模糊了,这又是为什么呢?

    canvas 的 css 宽高与上下文宽高

    在开头的代码中,canvas 标签是下面这样的:

    <canvas id="canvas" width="200" height="200"></canvas>
    

    canvas 标签中的 widthheight 属性并不是 css 中的宽高,而是 canvas 绘图上下文(绘图区域)的宽高,当不设置 canvas 的 css 宽高时,canvas 会将 widthheight 的值作为 css 宽高,而 css 宽高使元素在页面上的可见尺寸

    但是 canvas 的上下文宽高略奇怪,它可不管像素比是 1 是 2 还是 3,它就是会将整个 canvas 绘图区域塞进 css 宽高中并且填满,绘图的时候会将绘制的图形的宽高按照塞进 css 时宽与高的缩放比率分别进行缩放(所以如果缩放比率不同,就会导致绘制的图形变形

    但是上面这些都不是导致模糊的真正原因,下面这个才是捣乱的元凶:

    canvas 绘图时,会从两个物理像素的中间位置开始绘制并向两边扩散 0.5 个物理像素。当设备像素比为 1 时,一个 1px 的线条实际上占据了两个物理像素(每个像素实际上只占一半),由于不存在 0.5 个像素,所以这两个像素本来不应该被绘制的部分也被绘制了,于是 1 物理像素的线条变成了 2 物理像素,视觉上就造成了模糊

    解决绘图模糊的方法

    上面说了那么多弯弯绕,可能理解起来比较晕,但是解决的方法通过上面的内容却已经诞生了:

    首先分别声明 canvas 的 css 宽高和上下文宽高,同时上下文宽高应该是 css 宽高的 devicePixelRatio 倍。在上面的例子中就是:

    // devicePixelRatio = 2
    <style>
    canvas {
        width: 200px;
        height: 200px;
    }
    </style>
    <canvas id="canvas" width="400" height="400"></canvas>
    

    此时绘制一个与上面例子中相同的矩形,会是下面的效果:

    虽然是 100px * 100px 的矩形,但是由于绘制在 400px * 400px 的区域上,而画布又等比缩放在 200px * 200px 的 canvas 标签内,导致视觉上这个矩形只有 50px * 50px,绘制的线条宽度 1px 变成了 0.5 px,但因为无法绘制 0.5px 所以依然是通过 2 个物理像素绘制

    下一步,我们需要将 canvas 的绘图区域扩大一倍(因为 devicePixelRatio = 2),这样才能让视觉上的效果正常:

    ctx.scale(2, 2)
    ctx.strokeStyle = '#000'
    ctx.lineWidth = 1
    ctx.strokeRect(10, 10, 100, 100)
    

    效果如下:

    此时原先 0.5px 的线条变成了 1px,依然通过 2 个物理像素绘制,所以虽然扩大了一倍,但是边框宽度并不会改变

    需要注意的是,要在绘制之前先放大绘图区域,否则无效(不要忘了 canvas 的绘制是基于上下文状态的)

    到这里,我们就已经顺利解决了 canvas 绘图模糊的问题啦!如果你还没能理解,或许自己动手写一遍是很好的选择哦!

    扫码关注前端周记公众号

    相关文章

      网友评论

        本文标题:Canvas 绘图模糊问题解析

        本文链接:https://www.haomeiwen.com/subject/ieglbxtx.html