正文
最近接到公司项目中的一个需求,需要绘制一个展示管线平面图的功能,除了展示以外,还需要进行内容的编辑,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绘制管线管廊的资料请关注编程网其它相关文章!