Mouse position inside autoscaled SVG

Getting the correct svg mouse coordinate is tricky. First of all, a common way is to use the clientX and clientY of the event property an substract it with getBoundingClientRect() and clientLeft respectively clientTop.

svg.addEventListener('click', event =>
{
    let bound = svg.getBoundingClientRect();

    let x = event.clientX - bound.left - svg.clientLeft - paddingLeft;
    let y = event.clientY - bound.top - svg.clientTop - paddingTop;
}

But, if the svg has a padding style information greater then zero, the coordinate is shifting. So this information must be also substract:

let paddingLeft = parseFloat(style['padding-left'].replace('px', ''));
let paddingTop = parseFloat(style['padding-top'].replace('px', ''));

let x = event.clientX - bound.left - svg.clientLeft - paddingLeft;
let y = event.clientY - bound.top - svg.clientTop - paddingTop;

And the not so nice think is, that in some browsers the border property also shift the coordinate, and in other not. I found out, that the shift takes place if the x and y of the event property is not available.

if(event.x === undefined)
{
    x -= parseFloat(style['border-left-width'].replace('px', ''));
    y -= parseFloat(style['border-top-width'].replace('px', ''));
}

After this transformation the x and y coordinate can out of bound, that should be fix. But that not the think.

let width = svg.width.baseVal.value;
let height = svg.height.baseVal.value;

if(x < 0 || y < 0 || x >= width || y >= height)
{
    return;
}

This solution can use for click, mousemove, mousedown, ... and so on. You can reach a live demo here: https://codepen.io/martinwantke/pen/xpGpZB


See this code, which not only shows how to transform from screen space to global SVG space, but also how to transform a point from SVG space into the transformed space of an element:
http://phrogz.net/svg/drag_under_transformation.xhtml

In short:

// Find your root SVG element
var svg = document.querySelector('svg');

// Create an SVGPoint for future math
var pt = svg.createSVGPoint();

// Get point in global SVG space
function cursorPoint(evt){
  pt.x = evt.clientX; pt.y = evt.clientY;
  return pt.matrixTransform(svg.getScreenCTM().inverse());
}

svg.addEventListener('mousemove',function(evt){
  var loc = cursorPoint(evt);
  // Use loc.x and loc.y here
},false);

Edit: I've created a sample tailored to your needs (albeit only in global SVG space):
http://phrogz.net/svg/rotate-to-point-at-cursor.svg

It adds the following method to the above:

function rotateElement(el,originX,originY,towardsX,towardsY){
  var angle = Math.atan2(towardsY-originY,towardsX-originX);
  var degrees = angle*180/Math.PI + 90;
  el.setAttribute(
    'transform',
    'translate('+originX+','+originY+') ' +
      'rotate('+degrees+') ' +
      'translate('+(-originX)+','+(-originY)+')'
  );
}

Tags:

Javascript

Svg