fix: correct calculate rect bounding

This commit is contained in:
SikongJueluo 2025-06-13 20:08:46 +08:00
parent 1538bb9d07
commit dcadb97a7f
No known key found for this signature in database
1 changed files with 116 additions and 82 deletions

View File

@ -3,20 +3,19 @@
<v-stage class="h-full w-full" ref="stage" :config="stageSize" @mousedown="handleMouseDown" <v-stage class="h-full w-full" ref="stage" :config="stageSize" @mousedown="handleMouseDown"
@mousemove="handleMouseMove" @mouseup="handleMouseUp" @click="handleStageClick"> @mousemove="handleMouseMove" @mouseup="handleMouseUp" @click="handleStageClick">
<v-layer ref="layer"> <v-layer ref="layer">
<template v-for="item in list" :key="item.id"> <template v-for="item in objMap.values()" :key="item.id">
<v-group :config="{ <v-group :config="{
x: item.x, x: item.x,
y: item.y, y: item.y,
draggable: true, draggable: true,
id: `group-${item.id}`, id: `group-${item.id}`,
}" @dragstart="handleDragStart" @dragend="handleDragEnd"> }" @dragstart="handleDragStart" @dragend="handleDragEnd" @mouseover="handleCanvasObjectMouseOver">
<v-rect :config="{ <v-rect :config="item.config" />
x: item.box.x, <v-rect v-show="!isUndefined(item.box)" :config="{
y: item.box.y, ...item.box,
width: item.box.width, visible: !isUndefined(item.box),
height: item.box.height,
stroke: 'red', stroke: 'red',
strokeWidth: 1, strokeWidth: 3,
}"> }">
</v-rect> </v-rect>
</v-group> </v-group>
@ -56,50 +55,88 @@ const stageSize = {
height: window.innerHeight, height: window.innerHeight,
}; };
type CanvasObjectBox = {
x: number;
y: number;
width: number;
height: number;
};
type CanvasObject = { type CanvasObject = {
type: "Rect";
config: Konva.RectConfig;
id: string; id: string;
x: number; x: number;
y: number; y: number;
// rotation: number; box?: CanvasObjectBox;
// scale: number;
object: Konva.Node;
box: {
x: number;
y: number;
width: number;
height: number;
};
}; };
const list = ref<CanvasObject[]>([]); function calculateRectBounding(
width: number,
height: number,
rotation: number,
padding?: number,
) {
// calculate bounding box for rotated rectangle
const radians = (rotation * Math.PI) / 180;
const cos = Math.cos(radians);
const sin = Math.sin(radians);
// calculate corners of the rectangle
const corners = [
{ x: 0, y: 0 },
{ x: width, y: 0 },
{ x: width, y: height },
{ x: 0, y: height },
].map((point) => ({
x: point.x * cos - point.y * sin,
y: point.x * sin + point.y * cos,
}));
// find bounding box dimensions
const minX = Math.min(...corners.map((p) => p.x));
const maxX = Math.max(...corners.map((p) => p.x));
const minY = Math.min(...corners.map((p) => p.y));
const maxY = Math.max(...corners.map((p) => p.y));
if (padding)
return {
x: minX - padding,
y: minY - padding,
width: maxX - minX + padding * 2,
height: maxY - minY + padding * 2,
};
else
return {
x: minX,
y: minY,
width: maxX - minX,
height: maxY - minY,
};
}
const objMap = reactive<Map<string, CanvasObject>>(new Map());
onMounted(() => { onMounted(() => {
for (let n = 0; n < 100; n++) { for (let n = 0; n < 100; n++) {
const id = Math.round(Math.random() * 10000).toString(); const id = Math.round(Math.random() * 10000).toString();
const x = Math.random() * stageSize.width; const x = Math.random() * stageSize.width;
const y = Math.random() * stageSize.height; const y = Math.random() * stageSize.height;
const width = 30 + Math.random() * 30;
const height = 30 + Math.random() * 30;
const rotation = Math.random() * 180; const rotation = Math.random() * 180;
const scale = Math.random();
const star = new Konva.Star({ objMap.set(id, {
rotation: rotation, type: "Rect",
id: id, config: {
numPoints: 5, width: width,
innerRadius: 30, height: height,
outerRadius: 50, rotation: rotation,
fill: "#89b717", fill: "grey",
opacity: 0.8, id: id,
scaleX: scale, },
scaleY: scale,
});
list.value.push({
id: id, id: id,
x: x, x: x,
y: y, y: y,
// rotation: rotation,
// scale: scale,
object: star,
box: star.getClientRect(),
}); });
} }
}); });
@ -130,46 +167,6 @@ onMounted(() => {
selectedBox.rotateEnabled(false); selectedBox.rotateEnabled(false);
}); });
function degToRad(angle: number) {
return (angle / 180) * Math.PI;
}
function getCorner(
pivotX: number,
pivotY: number,
diffX: number,
diffY: number,
angle: number,
) {
const distance = Math.sqrt(diffX * diffX + diffY * diffY);
angle += Math.atan2(diffY, diffX);
const x = pivotX + distance * Math.cos(angle);
const y = pivotY + distance * Math.sin(angle);
return { x, y };
}
function getClientRect(element: Konva.Node) {
const { x, y, width, height, rotation } = element;
const rad = degToRad(rotation() ?? 0);
const p1 = getCorner(x(), y(), 0, 0, rad);
const p2 = getCorner(x(), y(), width(), 0, rad);
const p3 = getCorner(x(), y(), width(), height(), rad);
const p4 = getCorner(x(), y(), 0, height(), rad);
const minX = Math.min(p1.x, p2.x, p3.x, p4.x);
const minY = Math.min(p1.y, p2.y, p3.y, p4.y);
const maxX = Math.max(p1.x, p2.x, p3.x, p4.x);
const maxY = Math.max(p1.y, p2.y, p3.y, p4.y);
return {
x: minX,
y: minY,
width: maxX - minX,
height: maxY - minY,
};
}
function handleCacheChange(e: Event) { function handleCacheChange(e: Event) {
const target = e.target as HTMLInputElement; const target = e.target as HTMLInputElement;
const shouldCache = isNull(target) ? false : target.checked; const shouldCache = isNull(target) ? false : target.checked;
@ -192,12 +189,12 @@ function handleDragStart(e: Event) {
dragItemId.value = target.id(); dragItemId.value = target.id();
// move current element to the top: // move current element to the top:
const item = list.value.find((i) => i.id === dragItemId.value); // const item = list.value.find((i) => i.id === dragItemId.value);
if (isUndefined(item)) return; // if (isUndefined(item)) return;
//
const index = list.value.indexOf(item); // const index = list.value.indexOf(item);
list.value.splice(index, 1); // list.value.splice(index, 1);
list.value.push(item); // list.value.push(item);
} }
function handleDragEnd() { function handleDragEnd() {
@ -335,6 +332,43 @@ function handleMouseUp() {
selectedIds.value = selected.map((node: Konva.Node) => node.id()); selectedIds.value = selected.map((node: Konva.Node) => node.id());
} }
function handleCanvasObjectMouseOver(evt: Event) {
if (isNull(evt.target)) return;
const target = evt.target;
let object = null;
if (target instanceof Konva.Group) {
if (!target.hasChildren()) return;
object = target.children[0];
} else if (target instanceof Konva.Rect) {
object = target;
} else {
console.trace(`Not Konva class: ${target}`);
return;
}
// Get client rect
const objectConfig = objMap.get(object.id());
if (isUndefined(objectConfig)) {
console.error(`Not found object id: ${object.id()}`);
return;
}
if (isUndefined(objectConfig.box)) {
if (
objectConfig.config.width &&
objectConfig.config.height &&
objectConfig.config.rotation
) {
objectConfig.box = calculateRectBounding(
objectConfig.config.width,
objectConfig.config.height,
objectConfig.config.rotation,
5,
);
} else console.error("Could not calculate rect bounding");
}
}
</script> </script>
<style> <style>