1、draw_shape.js
1 /** 2 * 绘制不规则多边形 3 */ 4 5 import { Message } from 'element-ui' 6 7 export function draw_test(cav, list) { 8 // 画布初始化 9 let ctx = cav.getContext('2d') 10 ctx.strokeStyle = 'red' 11 ctx.lineWidth = 2 12 13 // 变量初始化 14 let isdraw = false // 是否在画图形 15 let endtip = false // 是否结束一个多边形的绘制 16 let coordinates = [] // 一个多边形信息 17 let point_in_area = false // 鼠标是否在区域内 18 let target_index = null // 目标索引 19 let is_select = false // 是否选中某个区域 20 let index_list = [] // 是否选中某个端点 21 let is_click = false // 是否按下鼠标 22 let click_point = {} // 按下鼠标的坐标 23 // 初始判断是否有框 24 draw_now_line(coordinates, ctx) // 把之前的点连线 25 draw_now_circle(coordinates, ctx) //画之前的点 26 if (list.length != 0) { 27 // 画所有的 28 draw_all_lines(list, ctx) 29 draw_all_circles(list, ctx) 30 fillarea(list, ctx) 31 } 32 33 // 鼠标按下 34 cav.onmousedown = e => { 35 if (e.which == 3) return 36 let x = e.offsetX 37 let y = e.offsetY 38 // 是否在端点 39 index_list = findClosestPointIndex(list, { x, y }) 40 if (index_list.length) { 41 console.log('在端点') 42 click_point = { x, y } 43 is_click = true 44 return 45 } 46 // 是否在区域内 47 if (point_in_area) { 48 ctx.clearRect(0, 0, 800, 600) 49 draw_all_lines(list, ctx) 50 draw_all_circles(list, ctx) 51 fillarea(list, ctx, target_index) 52 is_select = true 53 move_draw(list, ctx, cav, target_index, x, y) 54 return 55 } 56 is_select = false 57 if (endtip) { 58 if (list.length < 10) { 59 endtip = false //清空,重新画 60 } else { 61 Message.warning('最多可绘制20个闭合的凸多边形区域') 62 return 63 } 64 } 65 //获取鼠标按下的坐标,放入数组中 66 let insertFlag = true 67 coordinates.forEach((item, index) => { 68 if (item.x == x && item.y == y) { 69 insertFlag = false 70 } 71 }) 72 if (insertFlag) { 73 coordinates.push({ x: x, y: y }) 74 // 判断相交 75 let lineList = [] 76 coordinates.forEach((item, index) => { 77 if (index < coordinates.length - 1) { 78 lineList.push([item.x, item.y, coordinates[index + 1].x, coordinates[index + 1].y]) 79 } 80 }) 81 lineList.forEach((item, index) => { 82 if (index > 0 && index < lineList.length - 1) { 83 let flag = judgeIntersect( 84 lineList[lineList.length - 1][0], 85 lineList[lineList.length - 1][1], 86 lineList[lineList.length - 1][2], 87 lineList[lineList.length - 1][3], 88 lineList[index - 1][0], 89 lineList[index - 1][1], 90 lineList[index - 1][2], 91 lineList[index - 1][3] 92 ) 93 if (flag) { 94 coordinates.pop() 95 } 96 } 97 }) 98 } 99 draw_now_circle(coordinates, ctx) 100 fillarea(list, ctx) 101 isdraw = true //正在画多边形 102 } 103 104 // 鼠标抬起 105 cav.onmouseup = e => { 106 console.log(e) 107 is_click = false 108 click_point = {} 109 index_list = [] 110 console.log(point_in_area) 111 if (point_in_area) return 112 target_index = null 113 ctx.clearRect(0, 0, 800, 600) //清空画布 114 draw_now_line(coordinates, ctx) //把之前的点连线 115 draw_now_circle(coordinates, ctx) //画之前的点 116 if (list.length != 0) { 117 draw_all_lines(list, ctx) 118 draw_all_circles(list, ctx) 119 fillarea(list, ctx) 120 } 121 } 122 123 // 鼠标移动 124 cav.onmousemove = e => { 125 // 判断是否移动到某个区域内 126 let x = e.offsetX 127 let y = e.offsetY 128 // 判断是否在某个区域内 129 let point = { x, y } 130 if (list.length) { 131 for (let i = 0; i < list.length; i++) { 132 point_in_area = isPointInPolygon(list[i], point) 133 if (point_in_area) { 134 target_index = i 135 break 136 } 137 } 138 cav.style.cursor = point_in_area ? 'move' : 'crosshair' 139 } else { 140 point_in_area = false 141 ctx.clearRect(0, 0, 800, 600) //清空画布 142 cav.style.cursor = 'crosshair' 143 } 144 if (point_in_area) { 145 // console.log(point_in_area, target_index) 146 } else { 147 is_select = false 148 target_index = null 149 ctx.clearRect(0, 0, 800, 600) 150 draw_all_lines(list, ctx) 151 draw_all_circles(list, ctx) 152 fillarea(list, ctx) 153 } 154 // 编辑端点判断是否在端点时点击并移动 155 if (list.length) { 156 // list_index 局部变量 157 let list_index = findClosestPointIndex(list, point) 158 if (list_index.length) changeDraw(list, cav, ctx, list_index) 159 // index_list 全局变量 160 if (index_list.length) { 161 let target_data = { 162 x: click_point.x + (x - click_point.x), 163 y: click_point.y + (y - click_point.y), 164 } 165 list[index_list[0]].splice(index_list[1], 1, target_data) 166 return 167 } 168 } 169 index_list = [] 170 is_click = false 171 click_point = {} 172 //没开始画或者结束画之后不进行操作 173 if (coordinates.length == 0 || !isdraw || endtip) { 174 return 175 } 176 //获取上一个点 177 let last_x = coordinates[coordinates.length - 1].x 178 let last_y = coordinates[coordinates.length - 1].y 179 ctx.clearRect(0, 0, 800, 600) //清空画布 180 draw_now_line(coordinates, ctx) //把之前的点连线 181 draw_now_circle(coordinates, ctx) //画之前的点 182 if (list.length != 0) { 183 draw_all_lines(list, ctx) 184 draw_all_circles(list, ctx) 185 fillarea(list, ctx) 186 } 187 //获取鼠标移动时的点,画线,实现线段跟踪效果。 188 ctx.beginPath() 189 ctx.moveTo(last_x, last_y) 190 ctx.lineTo(x, y) //追踪鼠标 191 ctx.stroke() //绘制已定义的路径 追踪鼠标 192 ctx.closePath() 193 } 194 195 // 鼠标双击 196 cav.ondblclick = e => { 197 if (point_in_area) return 198 if (coordinates.length <= 2) return Message.warning('至少需要三个点,请继续绘制') 199 //双击画布,在最后一个点的时候双击,自动连线第一个点,同时宣告画结束 200 let x0 = coordinates[0].x 201 let y0 = coordinates[0].y 202 let x1 = coordinates[coordinates.length - 1].x 203 let y1 = coordinates[coordinates.length - 1].y 204 ctx.beginPath() 205 ctx.moveTo(x0, y0) 206 ctx.lineTo(x1, y1) 207 ctx.stroke() 208 ctx.closePath() 209 isdraw = false 210 endtip = true 211 list.push(coordinates) 212 ctx.fillStyle = 'transparent' 213 let bx = coordinates[0].x 214 let by = coordinates[0].y 215 ctx.beginPath() 216 ctx.moveTo(bx, by) 217 for (let k = 1; k < coordinates.length; k++) { 218 let x = coordinates[k].x 219 let y = coordinates[k].y 220 ctx.lineTo(x, y) 221 } 222 ctx.fill() 223 ctx.closePath() 224 coordinates = [] 225 } 226 227 // 鼠标右键 228 cav.oncontextmenu = e => { 229 e.preventDefault() 230 coordinates = [] 231 isdraw = false 232 endtip = true 233 clear_now_line(list, ctx) 234 } 235 236 // 键盘事件(删除区域) 237 cav.onkeydown = k => { 238 let key = k.keyCode || k.which 239 if ([8, 46].includes(key)) { 240 if (!is_select) return Message.warning('请先点击某个区域为选中状态,再删除') 241 list.splice(target_index, 1) 242 ctx.clearRect(0, 0, 800, 600) 243 if (list.length != 0) { 244 draw_all_lines(list, ctx) 245 draw_all_circles(list, ctx) 246 fillarea(list, ctx) 247 } else { 248 ctx.clearRect(0, 0, 800, 600) 249 } 250 } 251 } 252 } 253 254 /* 移动矩形 */ 255 function move_draw(list, ctx, cav, index, sX, sY) { 256 let mark = list[index] 257 let handle_move = em => { 258 let target_data = [] 259 mark.forEach(item => { 260 target_data.push({ 261 x: item.x + (em.offsetX - sX), 262 y: item.y + (em.offsetY - sY), 263 }) 264 }) 265 ctx.clearRect(0, 0, 800, 600) 266 list.splice(index, 1, target_data) 267 draw_all_lines(list, ctx) 268 draw_all_circles(list, ctx) 269 fillarea(list, ctx, index) 270 } 271 let handle_up = e => { 272 cav.removeEventListener('mousemove', handle_move) 273 } 274 // 无脑多移除几次,不然会出bug 275 cav.removeEventListener('mousemove', handle_move) // 先移除监听 276 cav.removeEventListener('mousemove', handle_move) // 先移除监听 277 cav.removeEventListener('mouseup', handle_up) // 先移除监听 278 cav.removeEventListener('mouseup', handle_up) // 先移除监听 279 cav.removeEventListener('mousemove', handle_move) // 先移除监听 280 cav.removeEventListener('mousemove', handle_move) // 先移除监听 281 cav.removeEventListener('mouseup', handle_up) // 先移除监听 282 cav.removeEventListener('mouseup', handle_up) // 先移除监听 283 cav.addEventListener('mousemove', handle_move) // 再重新监听,解决点击多次移除不干净的bug 284 cav.addEventListener('mouseup', handle_up) // 再重新监听,解决点击多次移除不干净的bug 285 } 286 287 /* 编辑多边形端点 */ 288 function changeDraw(list, cav, ctx, index_list) { 289 cav.style.cursor = 'pointer' 290 } 291 292 // 判断鼠标是否在端点 293 function findClosestPointIndex(list, points, type) { 294 let closestIndex = [] 295 let closestDistance = Infinity 296 for (let i = 0; i < list.length; i++) { 297 for (let j = 0; j < list[i].length; j++) { 298 let point = list[i][j] 299 let diffX = Math.abs(points.x - point.x) 300 let diffY = Math.abs(points.y - point.y) 301 let distance = Math.sqrt(diffX * diffX + diffY * diffY) 302 if (distance <= 2 && distance < closestDistance) { 303 closestIndex = [i, j] 304 closestDistance = distance 305 } 306 } 307 } 308 if (type == 'is') return closestIndex.length 309 return closestIndex 310 } 311 312 // 清除当前未画完成的区域 313 function clear_now_line(list, ctx) { 314 ctx.clearRect(0, 0, 800, 600) 315 if (list.length != 0) { 316 draw_all_lines(list, ctx) 317 draw_all_circles(list, ctx) 318 fillarea(list, ctx) 319 } 320 } 321 322 // 绘制选中的,颜色填充 323 function fillarea(list, ctx, index) { 324 for (var i = 0; i < list.length; i++) { 325 var cors = list[i] 326 var x0 = cors[0].x 327 var y0 = cors[0].y 328 ctx.beginPath() 329 ctx.fillStyle = i == index ? 'rgba(255,0,0,0.1)' : 'transparent' 330 ctx.moveTo(x0, y0) 331 for (var j = 1; j < cors.length; j++) { 332 var x = cors[j].x 333 var y = cors[j].y 334 ctx.lineTo(x, y) 335 } 336 ctx.fill() 337 ctx.closePath() 338 } 339 } 340 341 // 判断坐标点是否在区域内的函数 342 function isPointInPolygon(list, point) { 343 let isInside = false 344 // 射线与多边形边线的交点数量 345 let intersections = 0 346 for (let i = 0; i < list.length; i++) { 347 let coordinate1 = list[i] 348 let coordinate2 = list[(i + 1) % list.length] 349 // 判断射线与边线是否相交 350 if ( 351 coordinate1.y > point.y !== coordinate2.y > point.y && 352 point.x < 353 ((coordinate2.x - coordinate1.x) * (point.y - coordinate1.y)) / 354 (coordinate2.y - coordinate1.y) + 355 coordinate1.x 356 ) { 357 intersections++ 358 } 359 } 360 // 判断点是否在多边形内 361 if (intersections % 2 !== 0) { 362 isInside = true 363 } 364 return isInside 365 } 366 367 // 把当前绘制的多边形之前的坐标线段绘制出来 368 function draw_now_line(coordinates, ctx) { 369 for (let i = 0; i < coordinates.length - 1; i++) { 370 ctx.beginPath() 371 let x0 = coordinates[i].x 372 let y0 = coordinates[i].y 373 let x1 = coordinates[i + 1].x 374 let y1 = coordinates[i + 1].y 375 ctx.moveTo(x0, y0) 376 ctx.lineTo(x1, y1) 377 ctx.stroke() 378 ctx.closePath() 379 } 380 } 381 382 // 把当前绘制的多边形之前的端点画圆 383 function draw_now_circle(coordinates, ctx) { 384 ctx.fillStyle = 'red' 385 for (let i = 0; i < coordinates.length; i++) { 386 let x = coordinates[i].x 387 let y = coordinates[i].y 388 ctx.beginPath() //起始一条路径,或重置当前路径 389 ctx.moveTo(x, y) //把路径移动到画布中的指定点(x,y)开始坐标 390 ctx.arc(x, y, 5, 0, Math.PI * 2) //点 391 ctx.fill() //填充点 392 ctx.closePath() //创建从当前点回到起始点的路径 393 } 394 } 395 396 // 画出所有多边形 397 function draw_all_lines(list, ctx) { 398 for (let i = 0; i < list.length; i++) { 399 let cors = list[i] 400 //前后坐标连线 401 for (let j = 0; j < cors.length - 1; j++) { 402 ctx.beginPath() 403 let x0 = cors[j].x 404 let y0 = cors[j].y 405 let x1 = cors[j + 1].x 406 let y1 = cors[j + 1].y 407 ctx.moveTo(x0, y0) 408 ctx.lineTo(x1, y1) 409 ctx.stroke() 410 ctx.closePath() 411 } 412 //最后一个与第一个连线 413 let begin_x = cors[0].x 414 let begin_y = cors[0].y 415 let end_x = cors[cors.length - 1].x 416 let end_y = cors[cors.length - 1].y 417 ctx.fillStyle = 'transparent' 418 ctx.beginPath() 419 ctx.moveTo(begin_x, begin_y) 420 ctx.lineTo(end_x, end_y) 421 ctx.stroke() 422 ctx.closePath() 423 } 424 } 425 426 // 画出所有多边形的端点 427 function draw_all_circles(list, ctx, cav) { 428 ctx.fillStyle = 'red' 429 for (let i = 0; i < list.length; i++) { 430 let cors = list[i] 431 for (let j = 0; j < cors.length; j++) { 432 let x = cors[j].x 433 let y = cors[j].y 434 ctx.beginPath() 435 ctx.moveTo(x, y) 436 ctx.arc(x, y, 4, 0, Math.PI * 2) 437 ctx.fill() 438 ctx.closePath() 439 } 440 } 441 } 442 443 // 判断直线是否相交 444 function judgeIntersect(x1, y1, x2, y2, x3, y3, x4, y4) { 445 if ( 446 !( 447 Math.min(x1, x2) <= Math.max(x3, x4) && 448 Math.min(y3, y4) <= Math.max(y1, y2) && 449 Math.min(x3, x4) <= Math.max(x1, x2) && 450 Math.min(y1, y2) <= Math.max(y3, y4) 451 ) 452 ) 453 return false 454 let u, v, w, z 455 u = (x3 - x1) * (y2 - y1) - (x2 - x1) * (y3 - y1) 456 v = (x4 - x1) * (y2 - y1) - (x2 - x1) * (y4 - y1) 457 w = (x1 - x3) * (y4 - y3) - (x4 - x3) * (y1 - y3) 458 z = (x2 - x3) * (y4 - y3) - (x4 - x3) * (y2 - y3) 459 return u * v <= 0.00000001 && w * z <= 0.00000001 460 }
2、vue界面中使用
1 <template> 2 <div> 3 <div class="content"> 4 <img src="https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg" /> 5 <canvas id="mycanvas" ref="mycanvas" tabindex="0"></canvas> 6 </div> 7 <button @click="get_data">获取</button> 8 <button @click="reload_dom">复原</button> 9 </div> 10 </template> 11 12 <script> 13 import { draw_test } from '../utils/test_draw_box' 14 export default { 15 inject: ['reload'], 16 data() { 17 return { 18 isdraw: false, //是否在画图形 19 coordinates: [], //一个多边形的坐标信息 20 cor_index: 0, //当前多边形的索引 21 endtip: false, //是否结束一个多边形的绘制 22 // mark_list: [], //所有多边形的信息 23 mark_list: [ 24 [ 25 { 26 x: 135, 27 y: 49, 28 }, 29 { 30 x: 509, 31 y: 50, 32 }, 33 { 34 x: 230, 35 y: 198, 36 }, 37 ], 38 [ 39 { 40 x: 536, 41 y: 176, 42 }, 43 { 44 x: 318, 45 y: 344, 46 }, 47 { 48 x: 615, 49 y: 365, 50 }, 51 ], 52 [ 53 { 54 x: 64, 55 y: 357, 56 }, 57 { 58 x: 54, 59 y: 527, 60 }, 61 { 62 x: 227, 63 y: 524, 64 }, 65 { 66 x: 217, 67 y: 362, 68 }, 69 ], 70 ], //所有多边形的信息 71 PointInArea: false, 72 index: null, 73 } 74 }, 75 mounted() { 76 this.initCanvas() 77 }, 78 methods: { 79 /* 画布初始化 */ 80 initCanvas() { 81 this.$nextTick(() => { 82 // 初始化canvas宽高 83 let cav = this.$refs.mycanvas 84 cav.width = '800' 85 cav.height = '600' 86 let ctx = cav.getContext('2d') 87 ctx.strokeStyle = 'red' 88 cav.style.cursor = 'crosshair' 89 // 计算使用变量 90 let list = this.mark_list // 画框数据集合, 用于服务端返回的数据显示和绘制的矩形保存 91 // 若list长度不为0, 则显示已标记框 92 draw_test(cav, list) 93 }) 94 }, 95 get_data() { 96 console.log(this.mark_list) 97 }, 98 reload_dom() { 99 this.reload() 100 }, 101 }, 102 } 103 </script> 104 <style lang="scss" scoped> 105 .content { 106 position: relative; 107 // top: 50%; 108 // left: 50%; 109 // transform: translateX(-50%) translateX(-50%); 110 margin: 0 auto; 111 width: 800px; 112 height: 600px; 113 114 img { 115 position: absolute; 116 top: 0; 117 left: 0; 118 width: 100%; 119 height: 100%; 120 z-index: 9; 121 user-select: none; 122 } 123 124 canvas { 125 position: absolute; 126 top: 0; 127 left: 0; 128 z-index: 10; 129 } 130 } 131 // .main { 132 // height: 90vh; 133 // color: black; 134 // background: white; 135 // } 136 // #mycanvas { 137 // border: 1px solid red; 138 // position: fixed; 139 // left: 0; 140 // right: 0; 141 // margin: auto; 142 // } 143 </style>
界面效果:
参考地址1,画多边形:https://blog.csdn.net/m0_63853518/article/details/128301834
参考地址2,画矩形:https://blog.csdn.net/Sunshine_YXJ/article/details/108216971?spm=1001.2014.3001.5506