dota2pro
dota2pro

Reputation: 7864

How to draw a rectangle on SVG with mouse move?

I found code on someone's fiddle to stroke on mouse move (click and move strokes). My requirement is to stroke rectangle on a SVG with mouse move in the same way. Is it possible, if yes, how?

//Canvas
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
//Variables
let canvasx = canvas.offsetLeft;
let canvasy = canvas.offsetTop;
let last_mousex = 0;
let last_mousey = 0;
let mousex = 0;
let mousey = 0;
let mousedown = false;

//Mousedown
canvas.onmousedown = ({
  clientX,
  clientY
}) => {
  last_mousex = parseInt(clientX - canvasx);
  last_mousey = parseInt(clientY - canvasy);
  mousedown = true;
};

//Mouseup
canvas.onmouseup = () => mousedown = false;


//Mousemove

canvas.onmousemove = ({
  clientX,
  clientY
}) => {
  mousex = parseInt(clientX - canvasx);
  mousey = parseInt(clientY - canvasy);
  if (mousedown) {
    ctx.clearRect(0, 0, canvas.width, canvas.height); //clear canvas
    ctx.beginPath();
    const width = mousex - last_mousex;
    const height = mousey - last_mousey;
    ctx.rect(last_mousex, last_mousey, width, height);
    ctx.strokeStyle = 'black';
    ctx.lineWidth = 10;
    ctx.stroke();
  }
};
canvas {
  cursor: crosshair;
  border: 1px solid #000000;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>

<body>

  <canvas id="canvas" width="800" height="500"></canvas>

</body>

</html>

Some Code// To prevent Stackoverflow error, please ignore

Upvotes: 4

Views: 5904

Answers (3)

benvc
benvc

Reputation: 15130

In order to fully mimic the behavior of the canvas approach that enables you to draw a rectangle by clicking and dragging in any direction (i.e. lower right to top left or vice versa), you need to conditionally handle the x, y, width, and height values based on the position of the current mouse coordinates relative to the point of the initial mousedown. In addition, the snippet below includes a function that returns the correct coordinates if you are "drawing" on a transformed SVG element (or transformed child element).

const svg = document.querySelector('#svg');
const svgPoint = (elem, x, y) => {
  const p = svg.createSVGPoint();
  p.x = x;
  p.y = y;
  return p.matrixTransform(elem.getScreenCTM().inverse());
};

svg.addEventListener('mousedown', (event) => {
  const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
  const start = svgPoint(svg, event.clientX, event.clientY);
  const drawRect = (e) => {
    const p = svgPoint(svg, e.clientX, e.clientY);
    const w = Math.abs(p.x - start.x);
    const 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);
    rect.setAttributeNS(null, 'y', p.y);
    rect.setAttributeNS(null, 'width', w);
    rect.setAttributeNS(null, 'height', h);
    svg.appendChild(rect);
  };

  const endDraw = (e) => {
    svg.removeEventListener('mousemove', drawRect);
    svg.removeEventListener('mouseup', endDraw);
  };
  
  svg.addEventListener('mousemove', drawRect);
  svg.addEventListener('mouseup', endDraw);
});
svg {
  cursor: crosshair;
  border: 1px solid #000000;
}

rect {
  fill: none;
  stroke: #000000;
  stroke-width: 10;
}
<svg id="svg" width="800" height="500"></svg>

Upvotes: 10

dota2pro
dota2pro

Reputation: 7864

Tweaked Jason's answer to create rect element on the fly

const svg = document.querySelector('#svg');
const svgNS = svg.namespaceURI;
const rect = document.createElementNS(svgNS, 'rect');

let last_mousex = 0;
let last_mousey = 0;
let mousex = 0;
let mousey = 0;
let mousedown = false;

//Mousedown
svg.onmousedown = ({
  x,
  y
}) => {
  last_mousex = x;
  last_mousey = y;
  mousedown = true;
};

//Mouseup
svg.onmouseup = () => mousedown = false;

//Mousemove
svg.onmousemove = ({
  x,
  y
}) => {
  mousex = parseInt(x);
  mousey = parseInt(y);
  if (mousedown) {
    const width = Math.abs(mousex - last_mousex);
    const height = Math.abs(mousey - last_mousey);
    rect.setAttributeNS(null, 'x', last_mousex);
    rect.setAttributeNS(null, 'y', last_mousey);
    rect.setAttributeNS(null, 'width', width);
    rect.setAttributeNS(null, 'height', height);
    rect.setAttributeNS(null, 'fill', "none");
    rect.setAttributeNS(null, 'stroke', "black");
    rect.setAttributeNS(null, 'stroke-width', 5);



    // svg.innerHTML = "";
    svg.appendChild(rect);
  }
};
svg {
  cursor: crosshair;
  border: 1px solid #000000;
}
<svg id="svg" width="800" height="500">
</svg>

Upvotes: 1

Jason T
Jason T

Reputation: 37

const svg = document.querySelector('#svg');
const rect = document.querySelector('#rect');

let last_mousex = 0;
let last_mousey = 0;
let mousex = 0;
let mousey = 0;
let mousedown = false;

//Mousedown
svg.onmousedown = ({
  x,
  y
}) => {
  last_mousex = x;
  last_mousey = y;
  mousedown = true;
};

//Mouseup
svg.onmouseup = () => mousedown = false;

//Mousemove
svg.onmousemove = ({
  x,
  y
}) => {
  mousex = parseInt(x);
  mousey = parseInt(y);
  if (mousedown) {
    const width = Math.abs(mousex - last_mousex);
    const height = Math.abs(mousey - last_mousey);
    rect.setAttribute('x', last_mousex);
    rect.setAttribute('y', last_mousey);
    rect.setAttribute('width', width);
    rect.setAttribute('height', height);
    rect.style.fill = "none";
    rect.style.stroke = "black";
    rect.style['stroke-width'] = 5;
    svg.innerHTML = "";
    svg.appendChild(rect);
  }
};
svg {
  cursor: crosshair;
  border: 1px solid #000000;
}
<svg id="svg" width="800" height="500">
  <rect id="rect"></rect>
</svg>

Upvotes: 3

Related Questions