import React, {useEffect, useRef, useState} from "react";
import {Circle, Layer, Rect, Stage, Transformer} from "react-konva";
import Konva from "konva";
const App = () => {
const [rectPosition, setRectPosition] = useState({x: 50, y: 50});
const [circlePosition, setCirclePosition] = useState({x: 150, y: 150});
const [selectedIds, setSelectedIds] = useState([]);
const [isDragging, setIsDragging] = useState(false);
const stageRef = useRef(null);
const layerRef = useRef(null);
const transformerRef = useRef();
const selectionRectRef = useRef();
const selection = useRef({
visible: false,
x1: 0,
y1: 0,
x2: 0,
y2: 0,
});
const MIN_SCALE = 0.5;
const MAX_SCALE = 2;
const handleWheel = (e) => {
e.evt.preventDefault();
const stage = stageRef.current;
const oldScale = stage.scaleX();
const pointer = stage.getPointerPosition();
const scaleBy = 1.05;
let newScale = e.evt.deltaY > 0 ? oldScale * scaleBy : oldScale / scaleBy;
newScale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, newScale));
stage.scale({x: newScale, y: newScale});
const mousePointTo = {
x: (pointer.x - stage.x()) / oldScale,
y: (pointer.y - stage.y()) / oldScale,
};
const newPos = {
x: pointer.x - mousePointTo.x * newScale,
y: pointer.y - mousePointTo.y * newScale,
};
stage.position(newPos);
stage.batchDraw();
};
const updateSelectionRect = () => {
const node = selectionRectRef.current;
const stage = stageRef.current;
const scaleX = stage.scaleX();
const scaleY = stage.scaleY();
const pos = stage.position();
node.setAttrs({
visible: selection.current.visible,
x: (Math.min(selection.current.x1, selection.current.x2) - pos.x) / scaleX,
y: (Math.min(selection.current.y1, selection.current.y2) - pos.y) / scaleY,
width: Math.abs(selection.current.x1 - selection.current.x2) / scaleX,
height: Math.abs(selection.current.y1 - selection.current.y2) / scaleY,
fill: "rgba(0, 161, 255, 0.3)",
});
node.getLayer().batchDraw();
};
const onMouseDown = (e) => {
const isTransformer = e.target.findAncestor("Transformer");
if (isTransformer) {
return;
}
if (e.evt.shiftKey) {
const pos = e.target.getStage().getPointerPosition();
selection.current.visible = true;
selection.current.x1 = pos.x;
selection.current.y1 = pos.y;
selection.current.x2 = pos.x;
selection.current.y2 = pos.y;
updateSelectionRect();
setIsDragging(false);
} else {
setIsDragging(true);
}
};
const onMouseMove = (e) => {
if (!selection.current.visible) return;
const pos = e.target.getStage().getPointerPosition();
selection.current.x2 = pos.x;
selection.current.y2 = pos.y;
updateSelectionRect();
};
const onMouseUp = () => {
const selBox = selectionRectRef.current.getClientRect();
console.log("Selection box coordinates:", selBox);
const elements = [];
layerRef.current.find(".selectable").forEach((elementNode) => {
const elBox = elementNode.getClientRect();
console.log(`Element ${elementNode.id()} box coordinates:`, elBox);
if (Konva.Util.haveIntersection(selBox, elBox)) {
console.log(`Element ${elementNode.id()} intersects with selection box.`);
elements.push(elementNode);
}
});
if (elements.length > 0) {
console.log("Selected elements:", elements.map(el => el.id()));
} else {
console.log("No elements intersect with the selection box.");
}
setSelectedIds(elements.map((el) => el.id()));
transformerRef.current.nodes(elements);
selection.current.visible = false;
updateSelectionRect();
};
const onClickTap = (e) => {
const isShiftPressed = e.evt.shiftKey;
if (selectionRectRef.current.visible()) {
return;
}
const clickedOnEmpty = e.target === e.target.getStage();
if (clickedOnEmpty) {
setSelectedIds([]);
return;
}
const shape = e.target;
const isSelected = selectedIds.includes(shape.id());
if (isSelected) {
setSelectedIds((prevSelected) =>
prevSelected.filter((id) => id !== shape.id())
);
} else {
setSelectedIds((prevSelected) => [...prevSelected, shape.id()]);
}
};
useEffect(() => {
const transformer = transformerRef.current;
const nodes = selectedIds.map((id) => layerRef.current.findOne("#" + id));
if (nodes.length > 0) {
transformer.nodes(nodes);
transformer.getLayer().batchDraw();
} else {
transformer.nodes([]);
}
}, [selectedIds]);
return (
<div>
<Stage
ref={stageRef}
width={window.innerWidth}
height={window.innerHeight}
draggable={isDragging}
onWheel={handleWheel}
onMouseDown={onMouseDown}
onMouseMove={onMouseMove}
onMouseUp={onMouseUp}
onClick={onClickTap}
>
<Layer ref={layerRef}>
<Rect
id="rect1"
className="selectable"
x={rectPosition.x}
y={rectPosition.y}
width={100}
height={100}
fill="blue"
draggable
onDragEnd={(e) => setRectPosition({x: e.target.x(), y: e.target.y()})}
/>
<Circle
id="circle1"
className="selectable"
x={circlePosition.x}
y={circlePosition.y}
radius={50}
fill="green"
draggable
onDragEnd={(e) => setCirclePosition({x: e.target.x(), y: e.target.y()})}
/>
<Transformer ref={transformerRef}/>
<Rect ref={selectionRectRef}/>
</Layer>
</Stage>
</div>
);
};
export default App;
I started creating an application based on react-konva and after implementing a few basic features like drag and drop moving and zoom, I started creating a mechanism to select multiple items at the same time, but my selection area cannot catch any item when it is selected (onMouseUp) and I don't know what is wrong.
You are setting
className="selectable"
But you should use name, so
name="selectable"
The Konva.Stage.find() function uses the name attribute.
See the documentation for find here.