摸着文档过河的 Canvas 绘制

1,703 阅读5分钟

前言

这几天接了个活,需要做一个停车场示意图,首先想到的是使用 Canvas 来绘制。由于前期只是粗浅的使用过 Canvas,而没有正式的在项目中使用过,这次也是摸着文档过河😂。

如何绘制

Canvas 是画布,是一个需要脚本来绘制图形的 Html 标签。<canvas> 简单的使用

<canvas id="canvas"></canvas>

直接在页面上添加标签,必须使用脚本来绘制图形

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// 设置宽高
canvas.width = 500;
canvas.height = 500;

ctx.beginPath(); // 开始路径
ctx.rect(10, 10, 100, 100); // 绘制矩形

ctx.fillStyle = '#eee'; // 填充颜色
ctx.fill(); // 路径填充

可以看到一个简单的矩形会出现在画布中
image.png
其中 getContext 是获取 Canvas 对象;它有两个参数:

  • contextType 上下文类型(2d, webgl, webgl2)
  • contextAttributes 上下文属性;具体可参考MDN

画布存在默认高度 300 × 150,这里的高度设置是有一点需要注意的:如果我们使用 css 来设置高度时,画布会按照 300 × 150 的比例进行缩放,如果你设置 500 × 500 可能会变形。所以我们最好还是使用 Javascript 或者在标签上直接设置高宽度。

绘制步骤

从上面的一系列使用情况,我们可以总结出大概的绘制路径的方法

创建画布

使用 ID 来获取 canvas 对象

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

开始绘制路径

使用 beginPath 开始绘制

ctx.beginPath();

绘制路径

这里我们可以使用 Canvas 提供的各种绘制路径的方法;常见的一些绘制方法

方法描述
arc()创建圆弧
rect()创建矩形
fillRect()绘制矩形路径区域
strokeRect()绘制矩形路径描边
arcTo()创建两切线之间的弧/曲线
moveTo()把路径移动到画布中的指定点,不创建线条
lineTo()添加一个新点,然后在画布中创建从该点到最后指定点的线条
clip()从原始画布剪切任意形状和尺寸的区域
quadraticCurveTo()创建二次方贝塞尔曲线
bezierCurveTo()创建三次方贝塞尔曲线

填充或者描边

绘制完路径后,可以选择关闭路径 closePath 或者直接填充或描边路径,这些都是一些常规方法

ctx.fillStyle = '#eee'; // 填充颜色
ctx.fill(); // 路径填充

ctx.strokeStyle = 'red'; // 路径描边颜色
ctx.stroke(); // 路径描边

每个绘制的图形图案可以分成这基本的 4 步。我们需要了解的其实是每次绘制的坐标点,只要坐标点是确认的,绘制出来的图形还是没有什么误差。

画布操作

当把停车场绘制完成后,需要实现点击车位显示车位使用情况;由于点击 Canvas 只能监听到点击的坐标点,需要判断用户点击位置是否在车位内,而现在知道车位的起始点坐标与车位宽高,所以判断条件为

// 用户点击坐标 point
// 车位绘制信息 polylinePoints
  function checkPointInPolyline(point, polylinePoints) {
    // 当 x > 车位起始点 && x < 起始点 + 车位宽度 && y > 起始点 && y < 起始点 + 车位高度
    if (
      point.x >= polylinePoints.x &&
      point.x <= polylinePoints.x + polylinePoints.w &&
      point.y >= polylinePoints.y &&
      point.y <= polylinePoints.y + polylinePoints.h
    ) {
      return true;
    } else {
      return false;
    }
  }

我们都知道 canvas 在绘制的过程中,是以页面左上角原点 (0, 0) 开始绘制,但是由于停车场是一个垂直居中布局,当我们点击画布的原点得到的坐标是:
1213.gif
可以看到我们得到并不是接近 (0, 0) 的坐标点,这是由于鼠标是以 documnet 的左上角为原点获取坐标;因此如果点击车位中某一点时,需要对点击的某点 (x, y) 进行转换。
这里需要用到 getBoundingclientRect 这个函数,它是用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置;利用得到的 lefttop 值进行转换

canvas.addEventListener('click', function(e) {
  console.log(convertPoint(e.x, e.y))
})

function convertPoint(x, y) {
  var _info = canvas.getBoundingClientRect()
  return {
    x: x - _info.left,
    y: y - _info.top
  }
}

接下来可以看到,坐标点接近 (0, 0) 画布原点。
1213.gif

字体

在 Canvas 绘制字体,关键是使用 fillText(text, x, y [, maxWidth]) 函数:

  • text 是文本
  • x, y 是绘制的起始点
  • maxWidth 填充文本占据的最大宽度

一个完整的字体绘制也很简单

ctx.fillStyle = '#333';
ctx.font = '18px SimSun, Songti SC';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('imondo.cn', 200, 200);

image.png
但是我们想使用自定义字体时,使用常规的 @font-face 设置发现是没有用的,因为浏览器加载字体一般都是懒加载,在绘制的过程中,字体还未加载成功;这里推荐一个新的 API new FontFace,可以监听到字体加载,而且是个 Pormise

  const myFont = new FontFace('myFont', 'url(./webfont.ttf)');
  var _this = this;
  myFont
    .load()
    .then((font) => {
      document.fonts.add(font);
    })
    .then(function () {
      ctx.fillStyle = color;
      ctx.font = fontSize + ' myFont';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(text, x, y);
    });

兼容性还算可以,当然 IE 就不要考虑了(IE:感觉有被冒犯到 😂)

画布旋转缩放

Canvas 的旋转与缩放分别使用 rotatescale ,最需要注意的是它们都是基于默认中心点 (0, 0) 来旋转或缩放,所以在进行操作前,我们需要使用 translate 来改变中心点。
当我们需要旋转文字 90° 时,需要先改变中心点,再进行旋转,旋转后需要重新复原中心点,不然下次beginPath
绘制时,会基于 translate 后的中心点绘制

ctx.beginPath()
ctx.translate(200, 200);
ctx.rotate( 90 * Math.PI / 180)
ctx.translate(-200, -200);
ctx.fillStyle = '#333';
ctx.font = '18px SimSun, Songti SC';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('imondo.cn', 200, 200);

image.png
同理缩放操作也是一样

ctx.translate(200, 200);
ctx.scale(1.5, 1.5);
ctx.translate(-200, -200);

image.png
其实就是,绘制开始的坐标就是需要 translate 的坐标。

移动端模糊处理

当把整个停车场绘制完成后,看着还不错;不过当在手机上打开时,模糊的使人有几百度近视;查遍全网,还是移动端设备像素比的问题。解决方法就是检测设备像素比,绘制对应倍数比例的 canvas 元素

function createHDCanvas(w = 300, h = 150) {
  var ratio = window.devicePixelRatio || 1;
  var canvas = document.getElementById('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;
}

总结

整个绘制下来差不多也有半天多了,主要是对 Canvas 不熟悉,不过绘制完成后,对整个绘制过程有了一个更清晰的了解;对后期的一些业务技术还是有一定的储备😊。

最后附上:


参考: