【Excalidraw揭秘】canvas尺寸及分辨率矫正

3,257 阅读4分钟

本篇文章是excalidraw揭秘第一篇文章,内容较为基础。从本周开始会陆续更新excalidraw及canvas相关的知识。欢迎关注mini-excalidraw一起学习

前言

  • 当没有设置宽度和高度时,canvas 会初始化宽度为 300px 和高度为 150px,这是canvas的默认宽高
  • 可以使用 CSS 来定义 canvas 大小,但在绘制时 canvas 会伸缩以适应它的框架尺寸:如果 CSS 的尺寸与初始画布的比例不一致,它会出现扭曲。

在本篇文章中,我使用一个 100px * 100px 的红色正方形作为参照物,方便对比我们使用canvas绘制相同尺寸的图形大小是否正确。同时绘制了一条 1px 的线。如下图所示:

size-01.jpg

代码如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <title>Canvas尺寸</title>
    <style>
      .refer {
        width: 100px;
        height: 100px;
        background-color: red;
      }
      .border {
        border-top: 1px solid red;
        margin: 10px 0;
      }
    </style>
  </head>

  <body>
    <div id="root">
      <div class="refer">参照物</div>
      <div class="border"></div>
    </div>
  </body>
</html>

canvas 默认尺寸

我们先来看下,canvas 默认尺寸下,我们绘制的图形的尺寸有什么影响。

当没有设置宽度和高度时,canvas 会初始化宽度为 300px 和高度为 150px。此时,我们在 canvas 上绘制的图形尺寸和预期是一致的。比如绘制一个 100px * 100px的正方形,这个正方形的大小看起来就应该和我们的参照物(背景色红色的正方形)一样

这里我们添加一个 canvas,并在上面绘制一个 100px * 100px 的正方形

<style>
  .canvas {
    border: 1px solid black;
  }
</style>
<body>
  <div id="root">
    <div class="refer">参照物</div>
    <div class="border"></div>
    <canvas id="canvas" class="canvas"> 绘制canvas </canvas>
  </div>
  <script>
    const canvas = document.getElementById("canvas");
    const ctx = canvas.getContext("2d");

    ctx.strokeStyle = "red";
    ctx.lineWidth = 1;
    ctx.strokeRect(0.5, 0.5, 100, 100);
  </script>
</body>

效果如下

size-02.jpg

可以看到,在默认尺寸的画布中,我们绘制的图形尺寸是符合我们预期的,即大小正常。

注意,ctx.strokeRect(0.5, 0.5, 100, 100);是在(0.5, 0.5)坐标处开始绘制,而不是(0,0), 如果改成ctx.strokeRect(0, 0, 100, 100);,那么边框就会变得模糊

size-03.jpg

具体原因可以看canvas 线宽

通过 css 设置 canvas 尺寸

可以使用 CSS 来定义 canvas 大小,但在绘制时 canvas 会伸缩以适应它的框架尺寸:如果 CSS 的尺寸与初始画布的比例不一致,它会出现扭曲。从而导致绘制的图形变形。

我们可以使用css设置canvas大小。但如果我们设置的css宽高比例和canvas默认比例不一致,绘制出来的图形就会出现伸缩,变形。

继续使用上面的 demo 绘制同样的矩形,这次我们使用 css 给 canvas 设置宽高

<style>
  .canvas {
    width: 600px;
    height: 600px;
    border: 1px solid black;
  }
</style>
<body>
  <div id="root">
    <div class="refer">参照物</div>
    <div class="border"></div>
    <canvas id="canvas" class="canvas"> 绘制canvas </canvas>
  </div>
  <script>
    const canvas = document.getElementById("canvas");
    const ctx = canvas.getContext("2d");

    ctx.strokeStyle = "red";
    ctx.lineWidth = 1;
    ctx.strokeRect(0.5, 0.5, 100, 100);
  </script>
</body>

效果如下

size-04.jpg

分析

canvas 默认尺寸是宽 300 高 150,这里我们设置的尺寸是宽高都是 600。因此 canvas 在水平方向拉伸 2 倍,垂直方向拉伸 4 倍。反应到绘图中,就是我们原本的100px * 100px的矩形,就变成了 200px * 400px的矩形,绘制的图形严重变形。

如何正确设置 canvas 尺寸

如果需要通过 css 设置 canvas 尺寸,同时还要保证绘制的图形不变形,那么可以将 canvas 的 width 和 height 设置成相应的 css 宽度和高度,这样绘制出来的图形不会变形

<style>
  .canvas {
    width: 600px;
    height: 600px;
    border: 1px solid black;
  }
</style>
<body>
  <div id="root">
    <div class="refer">参照物</div>
    <div class="border"></div>
    <canvas id="canvas" class="canvas"> 绘制canvas </canvas>
  </div>
  <script>
    const canvas = document.getElementById("canvas");
    const ctx = canvas.getContext("2d");
    const { offsetWidth, offsetHeight } = canvas;
    // 重点
    canvas.width = offsetWidth;
    canvas.height = offsetHeight;

    ctx.strokeStyle = "red";
    ctx.lineWidth = 1;
    ctx.strokeRect(0.5, 0.5, 100, 100);
  </script>
</body>

效果如下:

size-05.jpg

小结

  • 当我们只通过css设置canvas宽高,而不设置canvas.width以及canvas.height时,canvas默认宽300高150,因此canvas本身会拉伸以适应css的宽高,那么绘制出来的图形会变形。
  • 当我们通过css设置canvas宽高时,同时将canvas.width和canvas.height设置成相同的css尺寸。此时的canvas就不需要拉伸以适应css的宽高,那么我们在上面绘制出来的图形不会变形,大小和我们预期一致

使用 window.devicePixelRatio 矫正 canvas 分辨率

先看下面的例子:

<style>
  .canvas {
    width: 600px;
    height: 600px;
    border: 1px solid black;
  }
</style>
<body>
  <div id="root">
    <div class="refer">参照物</div>
    <div class="border"></div>
    <canvas id="canvas" class="canvas"> 绘制canvas </canvas>
  </div>
  <script>
    const canvas = document.getElementById("canvas");
    const ctx = canvas.getContext("2d");
    const { offsetWidth, offsetHeight } = canvas;

    canvas.width = offsetWidth;
    canvas.height = offsetHeight;

    ctx.strokeStyle = "red";
    ctx.lineWidth = 1;
    ctx.strokeRect(0, 0, 100, 100);

    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineWidth = 1;

    ctx.lineTo(300, 150);
    ctx.stroke();
  </script>
</body>

size-06.jpg

可以看出绘制出来的图形和线段非常模糊。这是因为 canvas 在绘制的时候是按照 css 像素来绘制的,比如这里矩形就实际绘制了 100 个像素。而在高分辨率的屏幕中,这需要额外的实际像素绘制,比如在 window.devicePixelRatio = 2 的屏幕中,实际需要 canvas 绘制 200px 的像素。

因此我们需要矫正 canvas 分辨率。

按照MDN的定义,Window 接口的 devicePixelRatio 返回当前显示设备的物理像素分辨率与 CSS 像素分辨率之比。此值也可以解释为像素大小的比率:一个 CSS 像素的大小与一个物理像素的大小。简单来说,它告诉浏览器应使用多少屏幕实际像素来绘制单个 CSS 像素。

以我的电脑为例,window.devicePixelRatio 的值为 2,即浏览器需要使用 2 个屏幕实际像素来绘制单个 css 像素。看下面的例子:

<style>
  .canvas {
    width: 600px;
    height: 600px;
    border: 1px solid black;
  }
</style>
<body>
  <div id="root">
    <div class="refer">参照物</div>
    <div class="border"></div>
    <canvas id="canvas" class="canvas"> 绘制canvas </canvas>
  </div>
  <script>
    const canvas = document.getElementById("canvas");
    const ctx = canvas.getContext("2d");
    const { offsetWidth, offsetHeight } = canvas;

    canvas.width = offsetWidth * window.devicePixelRatio;
    canvas.height = offsetHeight * window.devicePixelRatio;
    ctx.scale(window.devicePixelRatio, window.devicePixelRatio);

    ctx.strokeStyle = "red";
    ctx.lineWidth = 1;
    ctx.strokeRect(0, 0, 100, 100);

    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineWidth = 1;

    ctx.lineTo(300, 150);
    ctx.stroke();
  </script>
</body>

效果如下:

size-07.jpg

结论

在使用 canvas 绘制图形时,需要使用 window.devicePixelRatio 矫正 canvas 分辨率,这样绘制出来的图形尺寸正确、清晰、不变形。在使用canvas时,矫正分辨率是必须要做的