How to resize SVG shapes with mouse in React

Viewed 266

I am using react with typescript. I am working on SVG's but I am facing an issue with it. I am able to draw shapes inside SVGs but I am facing a problem while resizing it.

I want to make my shapes resizable like if the user selects the right size or edge and drags the shape with a mouse then the shape will resize accordingly to cursor position.

I searched a lot on the Internet to find, How to know on which position the user clicked on SVG.

here is my code:

const divRef = useRef<HTMLDivElement>(null);
  const svgRef = useRef<SVGSVGElement>(null);

  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);

          containerSvg.appendChild(rect);
        };

        const endDraw = (e: any) => {
          containerSvg.removeEventListener("mousemove", drawRect);
          containerSvg.removeEventListener("mouseup", endDraw);

          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) {
            evt.stopPropagation();
            selectedRect = rect;
            containerSvg?.classList.add("dragging");
            selectedRect?.classList.add("target");
            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() {
            containerSvg?.classList.remove("dragging");
            selectedRect?.classList.remove("target");
            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 container" ref={divRef}>
      <svg id="svg" ref={svgRef}></svg>
    </div>
  );
1 Answers

You can change svg coordinates with user interaction and make it re-shape as what you want. My idea is to update svg coordinates with "useState and mouseEvent".

Follow steps as below!

  1. Make initial coordinates as state first, which means when the svg component first render, it renders with initial coordinates.
const [coordinates, setCoordinates] = useState({ x: 0, y: 0}) // the num you want.
  1. Make some functions to catch user interaction.
const handlePointerDown = (e) => {
 /// save initial mousePos to calculate coordinates.
}
const handlePointerMove = (e) => {
 /// calculate new coordinates logic
setCoordinates(x: newX, y: newY)
}

When user try to resize shape, eventListener catch the event and calculate new coordinates and then update it with useState. After setCoordinates executes, React re-make svg component with the new coordinates which means the component re-shape as user intent.

I skipped removeEventListener code above, you should put it if you need.

I hope you find a hint from my answer :D

Related