前言
这几天接了个活, 需要做一个停车场示意图, 首先想到的是使用 Canvas 来绘制. 由于前期只是粗浅的使用过 Canvas, 而没有正式的在项目中使用过, 这次也是摸着文档 https://www.canvasapi.cn/ 过河.
如何绘制
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(); // 路径填充
可以看到一个简单的矩形会出现在画布中
其中 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) 开始绘制, 但是由于停车场是一个垂直居中布局, 当我们点击画布的原点得到的坐标是:
可以看到我们得到并不是接近 (0, 0) 的坐标点, 这是由于鼠标是以 documnet 的左上角为原点获取坐标; 因此如果点击车位中某一点时, 需要对点击的某点 (x, y) 进行转换.
这里需要用到
getBoundingclientRect
这个函数, 它是用于获得页面中某个元素的左, 上, 右和下分别相对浏览器视窗的位置; 利用得到的 left,top 值进行转换
- 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) 画布原点.
字体
在 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);
但是我们想使用自定义字体时, 使用常规的 @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 的旋转与缩放分别使用 rotate ,scale , 最需要注意的是它们都是基于默认中心点 (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);
同理缩放操作也是一样
- ctx.translate(200, 200);
- ctx.scale(1.5, 1.5);
- ctx.translate(-200, -200);
其实就是, 绘制开始的坐标就是需要 translate 的坐标.
移动端模糊处理
当把整个停车场绘制完成后, 看着还不错; 不过当在手机上打开时, 模糊的使人有几百度近视; 查遍全网, 还是移动端设备像素比的问题. 解决方法就是检测设备像素比, 绘制对应倍数比例的 canvas 元素
- function createHDCanvas(w = 300, h = 150) {
- var ratio = Windows.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;
- }
效果 https://one-pupil.github.io/car_park/
源码: 地址 https://github.com/one-pupil/car_park
参考:
监听 Canvas 内部元素点击事件的三种方法 https://refined-x.com/2019/04/27/canvas-click/
聊聊 canvas 的元素选中 https://zhuanlan.zhihu.com/p/82730156
canvas 显示模糊问题 https://github.com/XXHolic/segment/issues/20
来源: https://segmentfault.com/a/1190000040039034