canvas根据坐标点位画图形-canvas拖拽编辑单个图形形状
创始人
2024-05-24 20:42:34
0
  1. 首先在选中图形的时候需要用鼠标右击来弹出选择框,实现第一个编辑节点功能
    在这里插入图片描述
  • 在components文件夹下新建右键菜单
    在这里插入图片描述
  • RightMenu文件:

  • 在页面中使用:
import RightMenu from '@/components/RightMenu/index';components: {RightMenu
},
  • 在data中定义所需要的变量
rightMenuType: '', //可操作图形状态
isRightMenu: false, //是否可以操作图形
  1. 鼠标右击的时候打开右键菜单
//鼠标右击
rightMenu(e) {if (this.type === 'move' && this.activeData.length > 0 && this.rightMenuType === '') {this.$refs.RightMenu.showModal(e.offsetX, e.offsetY);} else {return;}
},
  1. 点击选择选项接收值
//右键菜单返回
backRightMenu(e) {this.rightMenuType = e;this.isRightMenu = true;//编辑图形switch (e) {case 'editPoint':this.redrawMap();break;}
}
  1. redrawMap重绘过程中判断如果在编辑图形的状态,就选中图形并且图形顶点高亮
if(this.activeData.length > 0 && this.isRightMenu) {//编辑图形switch (this.rightMenuType) {case 'editPoint':drawMap.drawRectangle(this.activeData, 'editPoint');break;}
}
  • 效果如下:
    在这里插入图片描述
  1. 接下来实现吸附顶点,首先鼠标移动过程中判断是否吸附顶点,吸附状态下拖动点位可以更改图形点位坐标
// 开启吸附功能 记录是否处于吸附状态
if (this.activeData.length > 0 && ['editPoint'].includes(this.rightMenuType)) {this.activeData.map((item, idx) => {let result = mathUtils.attractPixel([x, y], item)if(result.isAttract && this.isMouseClick) {if(idx === 0 || idx === this.activeData.length - 1) {this.$set(this.activeData,this.activeData.length - 1,[x,y]);}this.$set(this.activeData,idx,[x,y]);}})
}
  • 涉及的算法:
// 计算两点距离
dealLength(start, end) {var a = end.x - start.x;var b = end.y - start.y;return Math.sqrt(a * a + b * b);
},
// 鼠标点击位置和目标点相距<=吸附半径则吸附到目标点
attractPixel(point1, pointTarget, pixelArea = adsorptionDistance) {const len = this.dealLength({x: point1[0],y: point1[1]}, {x: pointTarget[0],y: pointTarget[1]})const finalPoint = len <= pixelArea ? pointTarget : point1const isAttract = len <= pixelAreareturn {finalPoint,isAttract}
},
  • 效果如下:
    在这里插入图片描述
  1. 接下来实现在鼠标按下过程中如果触碰了图形的边线,就给点击边线的位置插入一个点位,形成多边形
//鼠标按下判断选中边线按下插入节点
if (this.activeData.length > 0 && ['editPoint'].includes(this.rightMenuType)) {const pointData = mathUtils.attractOnCheckLine({x,y}, this.activeData);if(pointData && pointData.overIdx >= 0) {this.activeData.splice(pointData.overIdx + 1, 0, [x, y])}
}
  • 效果如下:
    在这里插入图片描述
  • 涉及算法:
// 计算当前点到所有线段的垂点,小于5px,则吸附
attractOnCheckLine(point, coordinates) {for (var i = 0; i < coordinates.length; i++) {if (this.checkPoint(coordinates[i], point)) {return {x: coordinates[i][0],y: coordinates[i][1],idx: i};}}for (var i = 0; i < coordinates.length - 1; i++) {var pt = this.pointToSegDist(point.x, point.y, coordinates[i][0], coordinates[i][1],coordinates[i + 1][0], coordinates[i + 1][1], Math.pow(adsorptionDistance, 2));if (pt) {pt.overIdx = ireturn pt;}}return null;
},
checkPoint(target, point) {if (point.x >= target[0] - adsorptionDistance &&point.x <= target[0] + adsorptionDistance &&point.y >= target[1] - adsorptionDistance &&point.y <= target[1] + adsorptionDistance) {return true;} else {return false;}
},
pointToSegDist(x, y, x1, y1, x2, y2, dist) {var cross = (x2 - x1) * (x - x1) + (y2 - y1) * (y - y1);if (cross <= 0) return null;var d2 = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);if (cross >= d2) return null;var r = cross / d2;var px = x1 + (x2 - x1) * r;var py = y1 + (y2 - y1) * r;var dis = (x - px) * (x - px) + (py - y) * (py - y);if (dis <= dist) { // adsorptionDistance * adsorptionDistancereturn {x: px,y: py};}
},
  • 接下来插入的点位也可以进行拖拽了,大功告成!!!
    在这里插入图片描述

本节其他文件附下,复制可用:

  • 首页

  • mathUtils.js
import * as turf from "@/utils/turf.es";let adsorptionDistance = 6
const mathUtils = {// 计算两点距离dealLength(start, end) {var a = end.x - start.x;var b = end.y - start.y;return Math.sqrt(a * a + b * b);},// 计算点到线垂点的方法calculateVerticalPoint(arr) {const point = arr[2]var x1 = arr[0][0];var y1 = arr[0][1];var x2 = arr[1][0];var y2 = arr[1][1]if (x1 == x2 && y1 == y2) {return [point[0], point[1]];}var m = point[0];var n = point[1];var a = y2 - y1;var b = x1 - x2;var c = x2 * y1 - x1 * y2;var x3 = (b * b * m - a * b * n - a * c) / (a * a + b * b);var y3 = (a * a * n - a * b * m - b * c) / (a * a + b * b);return [Math.round(x3 * 100) / 100, Math.round(y3 * 100) / 100];},// 根据垂点计算平行点calculatePoint(vPoint, point, point2) {const x = point[0] - vPoint[0] + point2[0]const y = point[1] - vPoint[1] + point2[1]return [x, y]},// 判断点是否在多边形内部或者线上pointInPolygonORLine(point, polygon) {var pt = turf.point(point);var poly = turf.polygon([polygon]);return turf.booleanPointInPolygon(pt, poly)},// 获取多边形的中心点getPolygonCenter(arr) {var polygon = turf.polygon([arr]);var center = turf.centerOfMass(polygon);return center.geometry.coordinates},// 获取缩放后的坐标scalePoint(oGeo, scale, oCenter) {const newGeo = []const moveX = oCenter[0] * scale - oCenter[0]const moveY = oCenter[1] * scale - oCenter[1]for (var item of oGeo) {const x = item[0] * scale - moveXconst y = item[1] * scale - moveYnewGeo.push([x, y])}return newGeo},// 鼠标点击位置和目标点相距<=吸附半径则吸附到目标点attractPixel(point1, pointTarget, pixelArea = adsorptionDistance) {const len = this.dealLength({x: point1[0],y: point1[1]}, {x: pointTarget[0],y: pointTarget[1]})const finalPoint = len <= pixelArea ? pointTarget : point1const isAttract = len <= pixelAreareturn {finalPoint,isAttract}},// 计算当前点到所有线段的垂点,小于5px,则吸附attractOnCheckLine(point, coordinates) {for (var i = 0; i < coordinates.length; i++) {if (this.checkPoint(coordinates[i], point)) {return {x: coordinates[i][0],y: coordinates[i][1],idx: i};}}for (var i = 0; i < coordinates.length - 1; i++) {var pt = this.pointToSegDist(point.x, point.y, coordinates[i][0], coordinates[i][1],coordinates[i + 1][0], coordinates[i + 1][1], Math.pow(adsorptionDistance, 2));if (pt) {pt.overIdx = ireturn pt;}}return null;},checkPoint(target, point) {if (point.x >= target[0] - adsorptionDistance &&point.x <= target[0] + adsorptionDistance &&point.y >= target[1] - adsorptionDistance &&point.y <= target[1] + adsorptionDistance) {return true;} else {return false;}},pointToSegDist(x, y, x1, y1, x2, y2, dist) {var cross = (x2 - x1) * (x - x1) + (y2 - y1) * (y - y1);if (cross <= 0) return null;var d2 = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);if (cross >= d2) return null;var r = cross / d2;var px = x1 + (x2 - x1) * r;var py = y1 + (y2 - y1) * r;var dis = (x - px) * (x - px) + (py - y) * (py - y);if (dis <= dist) { // adsorptionDistance * adsorptionDistancereturn {x: px,y: py};}},
}export default mathUtils;
  • drawMap.js
let ctxDom, mapCtx; //初始化必要参数const drawMap = {//初始化地图initMap({id,w,h} = obj) {ctxDom = idid.width = wid.height = hmapCtx = id.getContext("2d");},//地图重绘redrawMap() {mapCtx.clearRect(0, 0, ctxDom.width, ctxDom.height);},//画圆drawCircle({x,y,r,strokeStyle = '#1289ff80', //边框色fillStyle = '#fff0', //填充色} = obj) {mapCtx.beginPath();mapCtx.fillStyle = fillStyle;mapCtx.setLineDash([]);mapCtx.strokeStyle = strokeStylemapCtx.arc(x, y, r, 0, 2 * Math.PI);mapCtx.closePath();mapCtx.stroke();mapCtx.fill();},drawRectangle(arr, isCheck) {mapCtx.strokeStyle = isCheck ? '#1289ff' : '#1289ff80';mapCtx.fillStyle = isCheck ? '#ffffff80' : '#fff0';mapCtx.lineWidth = 2;mapCtx.setLineDash([]);mapCtx.lineJoin = 'bevel';mapCtx.beginPath();mapCtx.moveTo(arr[0][0], arr[0][1]);for (let i = 1; i < arr.length; i++) {mapCtx.lineTo(arr[i][0], arr[i][1]);}mapCtx.stroke();mapCtx.fill();if (isCheck == 'editPoint') {for (let i = 0; i < arr.length; i++) {this.drawCircle({x: arr[i][0],y: arr[i][1],r: 3,strokeStyle: '#1289ff80',fillStyle: '#fff'})}}},
}export default drawMap

组件页请参考顶部!!!

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
修复 爱普生 EPSON L4... L4151 L4153 L4156 L4158 L4163 L4165 L4166 L4168 L4...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...