Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mouse position inside autoscaled SVG

Tags:

javascript

svg

I am experiencing troubles concerning the position of mouse cursor inside my SVG document. I'd like to design a potentiometer that will follow the cursor when dragged, using JavaScript in an HTML page.

I tried evt.clientX/Y and evt.screenX/Y but as my SVG is in autoscale, coordinates inside my SVG are different. I have been searching for an answer for days now but I couldn't find any solution (either knowing my SVG rescaling factor in real time or have a function for mouse location in SVG coordinates system).

The rotation will follow a simple rule:

if (evt.screenX < xc)   ang = Math.atan((evt.screenY - yc) / (evt.screenX - xc)) * 360/(2*Math.PI) - 90;    if (evt.screenX > xc)   ang = Math.atan((evt.screenY - yc) / (evt.screenX - xc)) * 360/(2*Math.PI) + 90;   

With (xc;yc) as center of rotation and replacing all evt.screenX/Y by the coordinates of the mouse inside my SVG.

like image 608
Riwall Avatar asked Apr 24 '12 13:04

Riwall


People also ask

How do I get my mouse cursor position?

Once you're in Mouse settings, select Additional mouse options from the links on the right side of the page. In Mouse Properties, on the Pointer Options tab, at the bottom, select Show location of pointer when I press the CTRL key, and then select OK. To see it in action, press CTRL.


2 Answers

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)+')'   ); } 
like image 89
Phrogz Avatar answered Sep 19 '22 08:09

Phrogz


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

like image 23
Martin Wantke Avatar answered Sep 19 '22 08:09

Martin Wantke