美文网首页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 绘图模糊问题解析

    初次使用 canvas 来绘图的小伙伴可能遇到过绘制出来的图像模糊不清的情况 我们先一起来绘制一个矩形 效果图如下...

  • Canvas绘图基础

    Canvas坐标系和绘图坐标系 Canvas绘图中牵扯到两种坐标系:Canvas坐标系与绘图坐标系。 Canvas...

  • canvas基础部分

    一、canvas的绘图表面 canvas有两种尺寸 canvas元素自身元素的尺寸 canvas绘图表面的尺寸 如...

  • 使用 canvas 绘图的几种方法

    canvas 标签 要使用 canvas 绘图,需要现在HTML中定义一个画布。 canvas 元素本身没有绘图能...

  • 三、Canvas基本绘图

    Canvas绘图(二) 本章将学习Canvas绘图的以下技巧 裁剪区域 图象合成 简单的Canvas变换 一、设置...

  • 完全自定义控件-Canvas之绘制基本形状

    一.简介 Canvas 美[ˈkænvəs] 画布 Canvas绘图有三个基本要素:Canvas、绘图坐标系...

  • canvas画图

    基本用法 canvas元素 获取绘图功能对象 canvas绘图方法 context.fill() // 填充co...

  • 二十二、Android绘图基础Canvas、Paint等

    Canvas 重新onDraw(Canvas canvas)方法时涉及绘图API :CanvasCanvas绘制方...

  • 2019-08-28 canvas

    canvas和SVG的区别:canvas使用它提供的方法来绘图,绘图API简洁。SVG通过构建XML元素数绘图,对...

  • Android绘图之Canvas变换(6)

    Android 绘图学习 1 Canvas 与屏幕 前面讲解了Canvas的基本概念,Android绘图之Canv...

网友评论

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

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