Saghar Francis
Saghar Francis

Reputation: 23

React typescript mouse events are overlapping

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:

here is a sandbox link

Upvotes: 1

Views: 855

Answers (1)

Hao Wu
Hao Wu

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


Edit

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

Related Questions