Canvas 是一个强大的工具,用于创建动态图形和交互式可视化内容。Vue.js 提供了便捷的双向数据绑定和组件化开发方式,使得在 Vue 中使用 Canvas 变得更加高效。本文将介绍如何在 Vue 2 中实现一个简单的多边形绘制工具,支持报警区域和安全区域的绘制。
1. 项目背景
在某些监控系统或地理信息系统(GIS)中,需要在地图或视频流上绘制报警区域和安全区域。这些区域通常以多边形的形式表示,用户可以通过鼠标绘制和编辑这些区域。本文将实现一个基于 Vue 2 和 Canvas 的绘制工具,支持以下功能:
-
绘制报警区域(单个多边形)。
-
绘制安全区域(多个多边形)。
-
动态绘制和闭合多边形。
-
清空绘制的区域。
2. 项目搭建
使用 Vue CLI 创建一个新的 Vue 项目:
vue create vue-canvas-drawing
cd vue-canvas-drawing
3. 绘制工具的实现
3.1 组件结构
我们将创建一个名为 DrawPolygon
的组件,用于实现绘制功能。组件的模板结构如下:
<template><div class="drawing-tool"><canvasref="myCanvas"class="canvas"@mousedown="handleCanvasMouseDown"@mouseup="handleCanvasMouseUp"@mouseleave="handleCanvasMouseUp"oncontextmenu="return false;"></canvas><div class="tool-buttons"><button @click="polygonType = 'alarm'; isDrawingPolygon = true">绘制报警区域</button><button @click="polygonType = 'safe'; isDrawingPolygon = true">绘制安全区域</button><button @click="handleClear('alarm')">清空报警区域</button><button @click="handleClear('safe')">清空安全区域</button></div></div>
</template>
3.2 数据结构
在 data
中定义绘制所需的数据:
javascript">data() {return {isDrawingPolygon: false, // 是否处于绘制状态polygonType: "", // 当前绘制的多边形类型("alarm" 或 "safe")currentShape: null, // 当前绘制的多边形detail: {alarm: [], // 报警区域(单个多边形)safe: [[]], // 安全区域(多个多边形)rate: 1, // 宽高比例scaleX: 1, // 水平缩放比例scaleY: 1 // 垂直缩放比例}};
},
3.3 绘制逻辑
3.3.1 handleCanvasMouseDown
方法
在鼠标按下时,初始化多边形的绘制:
javascript">handleCanvasMouseDown(event) {if (!this.isDrawingPolygon) return; // 如果没有在绘制状态,直接返回if (event.button !== 0) return; // 只响应左键点击const { x, y } = this.getCanvasCoords(event);if (this.polygonType === "alarm") {this.detail.alarm.push({ x, y });} else if (this.polygonType === "safe") {if (!this.currentShape) {this.detail.safe.push([]);this.currentShape = this.detail.safe[this.detail.safe.length - 1];}this.currentShape.push({ x, y });}this.drawShapes(); // 重新绘制
},
3.3.2 handleCanvasMouseUp
方法
处理鼠标释放事件,完成绘制:
javascript">handleCanvasMouseUp(event) {if (!this.isDrawingPolygon) return; // 如果没有在绘制状态,直接返回if (event.button !== 2) return; // 只响应右键点击this.finishDrawingPolygon();
},
3.3 .3 finishDrawingPolygon
方法
完成绘制,闭合多边形:
javascript">finishDrawingPolygon() {this.isDrawingPolygon = false; // 关闭绘制状态this.currentShape = null; // 清空当前绘制的多边形引用// 检查多边形的有效性(至少需要3个点)if (this.polygonType === "alarm" && this.detail.alarm.length < 3) {this.detail.alarm = []; // 如果点数不足,清空alarm区域} else if (this.polygonType === "safe") {const lastSafePolygon = this.detail.safe[this.detail.safe.length - 1];if (lastSafePolygon.length < 3) {this.detail.safe.pop(); // 如果点数不足,移除最后一个safe区域}}this.drawShapes("finish"); // 重新绘制所有多边形
},
3.3.4 dawShape方法
绘制多边形时,根据type
参数决定是否闭合路径:
javascript">drawShapes(type = "") {const canvas = this.$refs.myCanvas;const ctx = canvas.getContext("2d");ctx.clearRect(0, 0, canvas.width, canvas.height);// 绘制alarm区域(单个区域)if (this.detail.alarm.length > 1) {ctx.beginPath();ctx.moveTo(this.detail.alarm[0].x, this.detail.alarm[0].y);this.detail.alarm.forEach(point => {ctx.lineTo(point.x, point.y);});if (type === "finish") {ctx.closePath();}ctx.strokeStyle = "red"; // alarm区域用红色ctx.lineWidth = 2;ctx.stroke();}// 绘制safe区域(多个区域)this.detail.safe.forEach(region => {if (region.length > 1) {ctx.beginPath();ctx.moveTo(region[0].x, region[0].y);region.forEach(point => {ctx.lineTo(point.x, point.y);});if (type === "finish") {ctx.closePath();}ctx.strokeStyle = "green"; // safe区域用绿色ctx.lineWidth = 2;ctx.stroke();}});
},
3.3.5 坐标转换方法
将鼠标事件的坐标转换为Canvas坐标:
javascript">getCanvasCoords(event) {const canvas = this.$refs.myCanvas;const rect = canvas.getBoundingClientRect();return {x: (event.clientX - rect.left) * this.detail.scaleX,y: (event.clientY - rect.top) * this.detail.scaleY};
},
3.3.6 清空方法
javascript">handleClear(type) {if (type === "alarm") {this.detail.alarm = [];} else if (type === "safe") {this.detail.safe = [];}this.drawShapes();
},
4. 数据存储与获取
4.1 数据存储
在用户完成绘制后,可以将绘制的多边形数据发送到后端进行存储。
数据格式根据项目情况调整。
javascript">alarm 的数据格式:[{"x": 39.198113207547166,"y": 43.977987421383645},{"x": 31.556603773584904,"y": 125.4874213836478},{"x": 124.95283018867924,"y": 121.21069182389937},{"x": 39.198113207547166,"y": 43.977987421383645},{"x": 39.198113207547166,"y": 43.977987421383645},
]
javascript">safe的数据格式:
[[{"x": 180.14150943396226,"y": 54.795597484276726},{"x": 188.91509433962264,"y": 111.3993710691824},{"x": 257.40566037735846,"y": 95.04716981132076},{"x": 180.14150943396226,"y": 54.795597484276726}],[{"x": 227.4056603773585,"y": 23.852201257861637},{"x": 237.5943396226415,"y": 62.84591194968554},{"x": 268.4433962264151,"y": 60.07861635220126},{"x": 227.4056603773585,"y": 23.852201257861637}]
]
x,y 的最终结果,处理公式:
javascript">最终x = x / this.detail.scaleX * this.detail.rate
最终y = y / this.detail.scaleY * this.detail.rate