VUE使用canvas绘制管线管廊实现思路
作者:前端界的CV大师
正文
最近接到公司项目中的一个需求,需要绘制一个展示管线平面图的功能,除了展示以外,还需要进行内容的编辑,UI人员给的最终效果图如下:
经过分析后,觉得使用canvas,能够将此效果实现。最终将功能拆分为以下三点:
- canvas 绘制图形并填充
- canvas 绘制图片
- canvas 绘制文字
其中,绘制图形并填充对应的为管线,绘制图片对应的为设备图标,绘制文字则对应设备上方的文字描述及其他部分的文字信息,例如:一供、二回等。
因上述功能为项目中的功能的一部分,项目整体由VUE搭建,所以,此功能需要在VUE的项目结构上进行建立。
查阅大量资料及论坛后,没有找到合适的案例,那只能由本人亲自来手搓了,此案例涉及到以下知识点:
- JS的鼠标事件:
onmousedown
onmousemove
onmouseup
- canvas 坐标轴与浏览器坐标轴的计算
- canvas 实时绘制
- canvas 旋转
- 构造函数的运用
先分享一下最后的成果吧~
因时间及此案例代码量的关系
vue使用canvas绘制管线实现思路
首先,因canvas的绘制离不开JS,但是在vue页面中来书写,又会导致vue页面代码量过多,所以,我单独写了一个js文件,通过 export
进行导出,在vue页面中进行调用,下面来看代码:
index.vue
<template> <div class="canvas-container"> <div class="canvas-icon-content">左侧选项列表</div> <div class="canvas-content"> <div class="canvas" id="canvas"> <canvas id="myCanvas" ref="myCanvas"></canvas> </div> <div class="canvas-options">下方操作按钮</div> </div> </div> </template> <script> import canvas from '../utils/canvas' let myCanvas = {} export default { name: 'index', data() { return {} }, mounted() { myCanvas = canvas.init('myCanvas') }, } </script>
在 vue 页面中,主要是针对整体界面的搭建,css样式进行编写,其中除了界面外,还有 管线设备 信息修改的弹窗界面编写,如下图
针对信息编辑后的 “确定” 及 “取消” 事件,全部通过调用 myCanvas
中的方法来进行。
JS文件
在JS文件中,
首先定义一个 allElementCollection
数组,这个数组最终需要提交给后端,同时,页面中元素的绘制主要来自于这个主要数组。
剩下的就是来添加绘制的工作,以及JS中数据传入vue页面,vue页面的数据,传入JS中。
数据传输这里,我是这样做的,定义了一个对象 canvasDraw
,里面部分方法,如下代码:
const canvasDraw = { init(element) { canvas = document.getElementById(element) ctx = canvas.getContext('2d') const w = 1200, h = 800; canvas.width = w * devicePixelRatio; canvas.height = h * devicePixelRatio; canvas.style.width = w + 'px'; canvas.style.height = h + 'px'; return canvas }, // 回传鼠标抬起事件 canvasMouseUp: (e) => {}, // 绘制类型切换 drawTypeChange: (ele) => {}, // 修改管线类型(冷热水) changePipelineType: (type) => {}, // 设备参数修改 canvasModifyInfo: (info) => {}, // 显示设备可拖动的区域范围 showEquipmentIconArea: () => {}, commit: () => { // todo // 提交事件 }, // 清除整个画布 clearAll: (info) => {}, // 数据回显 echoData: (data) => {} } export default canvasDraw
上述代码中,所有的 canvas
相关的方法,都通过对象 canvasDraw
导出,在vue页面,就可以通过 myCanvas
来进行调用了。
接下来,我们需要一个构造函数,这个函数的作用是,通过构造函数,可以 new
多个对象,每个对象里面有鼠标按下的起点坐标 startX
、startY
,鼠标抬起的重点坐标 endX
、endY
,以及绘制的类型、绘制不同类型的信息对象、绘制形状的方法、绘制文字的方法、绘制图片的方法:
/** * 创建绘制元素工厂函数 * * */ class ElementFactory { constructor(startX, startY, endX, endY) { this.startX = startX; // 鼠标 按下 X点 this.startY = startY; // 鼠标 按下 Y点 this.endX = endX; // 鼠标 抬起 X点 this.endY = endY; // 鼠标 抬起 Y点 this.type = 0; // 绘制类型:图形、文字、图片 this.pipelineInfo = {}; // 图形(管线)私有信息 this.equipmentInfo = {}; // 图片(设备)私有信息 this.textInfo = {}; // 文字(文字)私有信息 } get minX() { return Math.min(this.startX, this.endX); } get maxX() { return Math.max(this.startX, this.endX); } get minY() { return Math.min(this.startY, this.endY); } get maxY() { return Math.max(this.startY, this.endY); } get middleX() { return this.endX - (this.endX - this.startX) / 2 } get middleY() { return this.endY - (this.endY - this.startY) / 2 } // 判断点击的是否存在元素绘制的范围之内 isInside(x, y) { return x >= this.minX && x <= this.maxX && y >= this.minY && y <= this.maxY } // 绘制管线 drawPipeline() {} // 绘制设备 drawEquipment() {} // 绘制设备上方文字 drawEquipmentText() {} // 绘制纯文本 drawText() {} // 根据条件来调用不同的绘制方法 drawAllElement() { parseInt(this.type) === 0 ? this.drawPipeline() : (parseInt(this.type) === 1 ? this.drawEquipment() : this.drawText()) } }
基本的方法已经写完了,那接下来,就剩下一些鼠标的管理事件了。
在函数 canvasMousedown
中,主要处理三件事情:
1、鼠标按下事件;
2、鼠标移动事件;
3、鼠标抬起事件。
鼠标按下的那一刻,有以下几个方面需要注意:
- 鼠标是左键点击还是右键点击;
- 当前鼠标点击的位置,即
startX
、startY
; - 当前鼠标点击的位置是否是存在了已经绘制的内容;
具体代码如下:
const canvasMousedown = (e) => { const rect = canvas.getBoundingClientRect(); const clickX = e.clientX - rect.left; const clickY = e.clientY - rect.top; // 查询所点击元素是否存在 const shape = getElement(clickX, clickY); if (shape) { moveAllElement(e, clickX, clickY, rect, shape); canvas.style.cursor= "move"; } else { if (e.buttons === 1) { draw_element_type === 0 ? drawRealTimePipeline(e, clickX, clickY, rect) : (draw_element_type === 1 ? drawRealTimeEquipment(e, clickX, clickY, rect) : drawRealTimeText(e, clickX, clickY, rect)) } } };
其中 getElement
方法为:
// 鼠标点击canvas查看是否点击到了已经绘制的路线,若是,则返回相关线的对象,若否,返回null const getElement = (x, y) => { for (let i = allElementCollection.length - 1; i >= 0; i--) { if (element.isInside(x, y)) return element; } return null };
鼠标按下后,获取到 clickX
、clickY
,判断当前点击的位置是否已经绘制了元素shape
,如果shape
存在,执行移动事件,如果不存在,则执行绘制事件。
大致的思路就是上述分享内容,接下来的文章中,我会将具体的方法及注意事项进行细化,
以上就是VUE使用canvas绘制管线管廊实现思路的详细内容,更多关于VUE canvas绘制管线管廊的资料请关注脚本之家其它相关文章!