<!-- * @FileDescription: vue-konva * @Author: hcy * @Date: 2023-03-29 --> <script setup lang="ts"> import { reactive, onMounted } from 'vue'; import { getBoundingBox } from 'src/common/utils'; import Konva from 'konva'; import Decimal from 'decimal.js'; const state = reactive({ stage: null as any, layer: null as any, group: null as any, // shape: null as any, // boundingBox: null as any, // box: null as any, shapePath: [ { name: 'test1', color: '#00D2FF', path: [ { x: 100, y: 100 }, { x: 200, y: 100 }, { x: 200, y: 200 }, { x: 0, y: 300 }, ], }, { name: 'test2', color: '#4CAF50', path: [ { x: 300, y: 200 }, { x: 200, y: 400 }, { x: 100, y: 600 }, { x: 400, y: 600 }, { x: 500, y: 200 }, ], }, { name: 'test3', color: '#FF8A80', path: [ { x: 600, y: 200 }, { x: 500, y: 400 }, { x: 300, y: 100 }, ], }, ] as any[], }); const stageSize = reactive({ width: 1200, height: 800, gridGap: 100, }); onMounted(() => { // const hit = Collide2D.collidePointPoint(100, 100, 100, 100); // console.log('hit', Collide2D); drawGrid(); handleData(); console.log('数据', state.shapePath); state.stage = new Konva.Stage({ container: 'stage-container', width: stageSize.width, height: stageSize.height, }); state.layer = new Konva.Layer(); state.stage.add(state.layer); for (const item of state.shapePath) { let shape = createShape(item); if (shape) { state.layer.add(shape); } } state.layer.on('dragmove', layerDragmove); }); function createShape(itemData: any) { const path = itemData.path || []; const fillColor = itemData.color || '#000000'; const name = itemData.name; const boundingBox = itemData.boundingBox || {}; const boundingBoxPath = itemData.boundingBox?.path || []; if (boundingBoxPath.length < 2) { console.warn('小于两个点,不能构成多边形,没有边界框'); } else { let group = new Konva.Group({ x: 0, y: 0, draggable: true, }); let rectBox = new Konva.Rect({ x: boundingBoxPath[0].x, y: boundingBoxPath[0].y, width: boundingBox.width, height: boundingBox.height, stroke: 'red', // fill: 'grey', strokeWidth: 4, }); // 如果添加到group,则坐标的相对位置时基于group的位置 let shape: any = new Konva.Shape({ sceneFunc: function (context, shape) { context.beginPath(); let index = 0; for (const i of path) { const x = i.x; const y = i.y; if (index === 0) { context.moveTo(x, y); } else { context.lineTo(x, y); } // context.quadraticCurveTo(150, 100, 260, 170); 弧线 index++; } context.closePath(); // (!) Konva specific method, it is very important context.fillStrokeShape(shape); }, fill: fillColor, name, // stroke: 'white', // strokeWidth: 10, }); group.add(rectBox); group.add(shape); return group; } } function layerDragmove(e: any) { let target = e.target; state.layer.children.forEach(function (group: any) { // do not check intersection with itself // 不检查与自身相交 if (group._id == target._id) { return; } }); } function handleData() { for (const item of state.shapePath) { item.path = removeAngleRepeat(item.path || []); item.boundingBox = getBoundingBox(item.path || []); } } // 一条线上有多个点,只保留起点和终点 function removeAngleRepeat(path: any[]) { let length = path.length; if (length >= 2) { let myMap = new Map(); let index = 1; for (const item of path) { let x1, y1, x2, y2; if (index < length) { x1 = item.x; y1 = item.y; x2 = path[index].x; y2 = path[index].y; } else { // 最后一个点连第一个点 x1 = item.x; y1 = item.y; x2 = path[0].x; y2 = path[0].y; } let du = getAngle(x1, y1, x2, y2, true); if (!myMap.has(du)) { myMap.set(du, item); } // console.log('du', du); index++; } let newArr: any[] = []; for (const value of myMap.values()) { newArr.push(value); } return newArr; } else { return path; } } /** * 计算从x1y1到x2y2的直线,与水平线形成的夹角 * 计算规则为顺时针从左侧0°到与该直线形成的夹角 * @param {Object} x1 * @param {Object} y1 * @param {Object} x2 * @param {Object} y2 * @param {Boolean} toAngle 是否转换为角度值,默认false */ function getAngle( x1: number, y1: number, x2: number, y2: number, toAngle = false ) { let x = Decimal.sub(x1, x2); let y = Decimal.sub(y1, y2); if (!x.toNumber() && !y.toNumber()) { return 0; } // 弧度 radian = 角度 * Math.PI / 180 // 角度 angle = 弧度 * 180 / Math.PI let res; // 角度 // let angle = (180 + (Math.atan2(-y, -x) * 180) / Math.PI + 360) % 360; let _atan2 = Decimal.atan2(y.negated(), x.negated()); let _angle = Decimal.div(Decimal.mul(_atan2, 180), Math.PI); let angle = Decimal.mod(Decimal.add(180, _angle).plus(360), 360); res = Decimal.sub(360, angle); if (!toAngle) { res = Decimal.mul(res, Math.PI).div(180); } return res.toNumber(); } // 网格线 function drawGrid() { let canvas: any = document.getElementById('canvas-grid'); let pen = canvas.getContext('2d'); // 绘制网格 const step = stageSize.gridGap; const h = stageSize.height; const w = stageSize.width; const w_l = w / step; const h_l = h / step; // 横着的线 for (let i = 0; i <= h_l; i++) { pen.beginPath(); pen.moveTo(0, i * step); pen.lineTo(w, i * step); pen.stroke(); } // 竖着的线 for (let i = 0; i <= w_l; i++) { pen.beginPath(); pen.moveTo(i * step, 0); pen.lineTo(i * step, h); pen.stroke(); } } </script> <template> <div class="konva-main-page container-height center"> <div>666</div> <div class="canvas-box"> <canvas id="canvas-grid" :width="stageSize.width" :height="stageSize.height" style="position: absolute" ></canvas> <div id="stage-container"></div> </div> </div> </template> <style lang="scss" scoped> .konva-main-page { } .canvas-box { box-sizing: border-box; width: 1200px; height: 800px; border: 1px solid #000; // background: pink; } </style>