Reputation: 23
I am using react with typescript. I use SVG to draw rectangles inside it. I am working on two features, the first one is I have to draw any number of shapes inside the SVG, and the other one is to allow mouse drag option. Now, the problem is when even I am drawing a shape and then drawing another shape in it the first drawn shape is moving and the new shape is drawing.
I want to do if I click and move the shape my drawing rectangle functionality should not work and if I am drawing the rectangle the already drawn shape would not move. this happening because I am using mouseup and mousemove events for both logic and that is why they collapsing. I don't know to separate them.
here is my code:
import "./styles.css";
import React, { useEffect, useRef } from 'react';
interface Iboxes{
id: string,
coordinates:{
x: number
y: number
width: number
height: number
}
}
export default function App() {
const divRef = useRef<HTMLDivElement>(null);
const svgRef = useRef<SVGSVGElement>(null);
const boxes: Array<Iboxes> = [];
useEffect(() => {
const containerSvg = svgRef.current;
let p:DOMPoint;
let w:number;
let h:number;
if(containerSvg){
const svgPoint = (elem:any, x:number, y:number) => {
const point = containerSvg.createSVGPoint();
point.x = x;
point.y = y;
return point.matrixTransform(elem.getScreenCTM().inverse());
};
containerSvg.addEventListener('mousedown', (event) => {
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
const start = svgPoint(containerSvg, event.clientX, event.clientY);
const drawRect = (e:any) => {
p = svgPoint(containerSvg, e.clientX, e.clientY);
w = Math.abs(p.x - start.x);
h = Math.abs(p.y - start.y);
if (p.x > start.x) {
p.x = start.x;
}
if (p.y > start.y) {
p.y = start.y;
}
rect.setAttributeNS(null, 'x', p.x as unknown as string);
rect.setAttributeNS(null, 'y', p.y as unknown as string);
rect.setAttributeNS(null, 'width', w as unknown as string);
rect.setAttributeNS(null, 'height', h as unknown as string);
rect.setAttributeNS(null, 'class', 'svg_rec');
rect.setAttributeNS(null, 'id', 'rec_' + boxes.length as unknown as string)
containerSvg.appendChild(rect);
};
const endDraw = (e:any) => {
containerSvg.removeEventListener('mousemove', drawRect);
containerSvg.removeEventListener('mouseup', endDraw);
boxes.push({id:'rec_' + boxes.length as unknown as string, coordinates:{x: p.x, y: p.y, width: w, height: h}})
let offset:any;
let selectedRect: SVGRectElement | null;
rect.addEventListener('mousedown', startDrag);
rect.addEventListener('mousemove', drag);
rect.addEventListener('mouseup', endDrag);
rect.addEventListener('mouseleave', endDrag);
function startDrag(evt:any) {
selectedRect = rect;
if(selectedRect){
offset = getMousePosition(evt);
if(offset){
let rectX = selectedRect.getAttributeNS(null, 'x');
let rectY = selectedRect.getAttributeNS(null, 'y');
if(rectX && rectY){
offset.x -= parseFloat(rectX);
offset.y -= parseFloat(rectY);
}
}
}
}
function drag(evt:any) {
if(selectedRect){
var coord = getMousePosition(evt);
if(coord && offset){
let x = coord.x - offset.x;
let y = coord.y - offset.y;
if(x && y){
selectedRect.setAttributeNS(null, "x", x as unknown as string);
selectedRect.setAttributeNS(null, "y", y as unknown as string);
}
}
}
}
function endDrag() {
selectedRect = null;
}
function getMousePosition(evt:any) {
var CTM = rect.getScreenCTM();
if(CTM){
return {
x: (evt.clientX - CTM.e) / CTM.a,
y: (evt.clientY - CTM.f) / CTM.d
};
}
}
};
containerSvg.addEventListener('mousemove', drawRect);
containerSvg.addEventListener('mouseup', endDraw);
});
}
},[]);
return (
<div className="App">
<div className='container' ref={divRef}>
<svg id="svg" ref={svgRef}>
</svg>
</div>
</div>
);
}
I also created a sandbox environment to demonstrate my issue:
Upvotes: 1
Views: 855
Reputation: 20699
You just need to add
evt.stopPropagation();
to the function startDrag()
on line 79
.
function startDrag(evt: any) {
evt.stopPropagation();
selectedRect = rect;
// ...
}
Here's the fixed sandbox
Because other rects on top may obscure the current dragging rect. One solution is to disable the pointer-events
for other rects while dragging one rect:
Add following code to the startDrag
function
containerSvg.classList.add("dragging");
selectedRect?.classList.add("target");
and then add following code to the endDrag
function
containerSvg.classList.remove("dragging");
selectedRect?.classList.remove("target");
then in your css code, add following rule:
.dragging rect:not(.target) {
pointer-events: none;
}
Checked updated sandbox
Upvotes: 1