SVG how to get the mouse position on the internal matrix

ServerBloke picture ServerBloke · Feb 18, 2014 · Viewed 8k times · Source

jsFiddle: http://jsfiddle.net/az6Ug/

Using Snap.svg library, I want drag on the SVG and get the mouse position. I need the mouse position on the internal matrix, not the browser mouse position. I have this working with one problem, which is if there is any scroll on the window that the SVG belongs to, the calculated mouse position is offset by the amount of scroll on the scrollbar.

For example, with no scroll then it works fine. Or with 10px of vertical scroll on the scrollbar, the mouse position is calculated as: true position + 10px. It's the same deal if there's any horizontal scroll: offset by the amount of scroll.

In the jsFiddle, I use a rectangle to show the calculared mouse position while dragging. As you can see if there's no scroll, then the rectangle stays with the mouse cursor (upper left corner). But with some scroll, the rectangle is offset from the mouse cursor.

Although I'm using the Snap.svg library, I only use this to get the drag event, the mouse calculation is pure Javascript. A possible solution is to subtract the X and Y by the amount of scroll but I think there will be a better way using the SVG's tranform functions.

var S;
var pt;
var svg
var box;

window.onload = function(){
    svg = $('#mysvg')[0];
    S = Snap(svg);

    pt = pt = svg.createSVGPoint(); // create the point

    // add the rectangle
    box = S.rect(10, 10, 50, 50);
    box.attr({ fill : 'red', stroke : 'none' });

    S.drag(
        function(dx, dy, posX, posY, e){
            //onmove
            pt.x = posX;
            pt.y = posY;

            // convert the mouse X and Y so that it's relative to the svg element
            var transformed = pt.matrixTransform(svg.getScreenCTM().inverse());
            box.attr({ x : transformed.x, y : transformed.y });
        },
        function(){
            //onstart
        },
        function(){
            //onend
        }
    );

}

Answer

Francis Hemsher picture Francis Hemsher · Feb 18, 2014

Below is an example in Javascript of what I use across all browsers and all viewPorts. It uses svg matrix transforms. The events are attached to each element to be dragged. In the example I've included mouse position readout both for html and svg.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>E - Universal Drag/Drop</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body style='padding:10px;font-family:arial'>
<center>
<h4>Universal Drag/Drop</h4>
<div style='width:90%;background-color:gainsboro;text-align:justify;padding:10px;border-radius:6px;'>
This example uses matrix transforms, with object methods,  not strings. It can seamlessly drag/drop elements that have previously been transformed and reside it different viewPorts. It employs <b>getScreenCTM</b>,  <b>createSVGTransform</b> and binds the element to a <b>transform List</b>
</div>
<table>
<tr><td align=left>
 Scenerio:<br />
A 400x400 DIV contains an SVG with viewBox=0 0 330 330.<br />
1.) The blue rect element is contained in a &lt;g&gt;.<br />
2.) The &lt;g&gt; element has been transformed.<br />
3.) The maroon rect resides in a different viewPort.<br />
4.) The orange circle has been transformed.<br />
5.) Drag/Drop the circles and rectangles.<br />
</td>
<td align=left>
<div id="svgDiv" style='background-color:lightgreen;width:400px;height:400px;'>
<svg id="mySVG" width="100%" height="100%" viewBox="0 0 300 300" onmousemove="svgCursor(evt)">
<circle onmousedown=startDrag(evt) onmousemove=drag(evt) onmouseup=endDrag() id="redCircle" cx="120" cy="180" r="40" fill="red" stroke="black" stroke-width="2" />
<circle onmousedown=startDrag(evt) onmousemove=drag(evt) onmouseup=endDrag() id="orangeCircle" cx="200" cy="200" r="40" fill="orange" stroke="black" stroke-width="2" />
<svg viewBox="0 100 800 800">
<rect onmousedown=startDrag(evt) onmousemove=drag(evt) onmouseup=endDrag() id="maroonRect"  x="220" y="250" width="60" height="60" fill="maroon" stroke="black" stroke-width="2"  />
</svg>
<g id="myG" >
<rect onmousedown=startDrag(evt) onmousemove=drag(evt) onmouseup=endDrag()     id="blueRect"  x="220" y="250" width="60" height="60" fill="blue" stroke="black" stroke-width="2"  />
</g>
</svg>
</div>
</td>
<td align=left>
<b>HTML Page Values:</b><br />
<input type=text id=htmlMouseXValue size=1 />: mouse X<br />
<input type=text id=htmlMouseYValue size=1 />: mouse Y<br />
<br />
<b>SVG Image Values:</b><br />
<input type=text id=svgXValue size=1 />: svg X<br />
<input type=text id=svgYValue size=1 />: svg Y<br />
</td>
</tr></table>
<br />SVG Source:<br />
<textarea id=svgSourceValue style='font-size:110%;font-family:lucida console;width:90%;height:200px'></textarea>
<br />Javascript:<br />
<textarea id=jsValue style='border-radius:26px;font-size:110%;font-weight:bold;color:midnightblue;padding:16px;background-color:beige;border-width:0px;font-size:100%;font-family:lucida console;width:90%;height:400px'></textarea>
</center>
<div id='browserDiv' style='padding:3px;position:absolute;top:5px;left:5px;background-color:gainsboro;'></div>
<script id=myScript>
var TransformRequestObj
var TransList
var DragTarget=null;
var Dragging = false;
var OffsetX = 0;
var OffsetY = 0;
//---mouse down over element---
function startDrag(evt)
{
    if(!Dragging) //---prevents dragging conflicts on other draggable elements---
    {
        DragTarget = evt.target;
        //---reference point to its respective viewport--
        var pnt = DragTarget.ownerSVGElement.createSVGPoint();
        pnt.x = evt.clientX;
        pnt.y = evt.clientY;
        //---elements transformed and/or in different(svg) viewports---
        var sCTM = DragTarget.getScreenCTM();
        var Pnt = pnt.matrixTransform(sCTM.inverse());

        TransformRequestObj = DragTarget.ownerSVGElement.createSVGTransform()
        //---attach new or existing transform to element, init its transform list---
        var myTransListAnim=DragTarget.transform
        TransList=myTransListAnim.baseVal

        OffsetX = Pnt.x
        OffsetY = Pnt.y

        Dragging=true;
    }
}
//---mouse move---
function drag(evt)
{
    if(Dragging)
    {
        var pnt = DragTarget.ownerSVGElement.createSVGPoint();
        pnt.x = evt.clientX;
        pnt.y = evt.clientY;
        //---elements in different(svg) viewports, and/or transformed ---
        var sCTM = DragTarget.getScreenCTM();
        var Pnt = pnt.matrixTransform(sCTM.inverse());
        Pnt.x -= OffsetX;
        Pnt.y -= OffsetY;

        TransformRequestObj.setTranslate(Pnt.x,Pnt.y)
        TransList.appendItem(TransformRequestObj)
        TransList.consolidate()
    }
}
//--mouse up---
function endDrag()
{
    Dragging = false;
    svgSourceValue.value=svgDiv.innerHTML
}
//---onload---
function initTransforms()
{
//---place some transforms on the elements---

    //--- transform orange circle---
    var transformRequestObj=mySVG.createSVGTransform()
    var animTransformList=orangeCircle.transform
    var transformList=animTransformList.baseVal
    //---translate---
    transformRequestObj.setTranslate(130,-300)
    transformList.appendItem(transformRequestObj)
    transformList.consolidate()
    //----scale---
    transformRequestObj.setScale(.5,.9)
    transformList.appendItem(transformRequestObj)
    transformList.consolidate()
    //----skewY---
    transformRequestObj.setSkewY(52)
    transformList.appendItem(transformRequestObj)
    transformList.consolidate()

    //--init Transform on myG---
    var transformRequestObj=mySVG.createSVGTransform()
    var animTransformList=myG.transform
    var transformList=animTransformList.baseVal
    //---translate---
    transformRequestObj.setTranslate(-50,-120)
    transformList.appendItem(transformRequestObj)
    transformList.consolidate()
    //----skewX---
    transformRequestObj.setSkewX(15)
    transformList.appendItem(transformRequestObj)
    transformList.consolidate()
    //----skewY---
    transformRequestObj.setSkewY(20)
    transformList.appendItem(transformRequestObj)
    transformList.consolidate()
    //---rotate---
    transformRequestObj.setRotate(30,200,200)
    transformList.appendItem(transformRequestObj)
    transformList.consolidate()

}

document.onmousemove = htmCursor
//---event is the html event object---
function htmCursor(event)
{
    var event = event || window.event;
    myMouseX=event.clientX;
    myMouseY=event.clientY;
    myMouseX = myMouseX + document.documentElement.scrollLeft;
    myMouseY = myMouseY + document.documentElement.scrollTop;

    htmlMouseXValue.value=myMouseX
    htmlMouseYValue.value=myMouseY
}
//---evt is the svg event object--
function svgCursor(evt)
{
    var rect = svgDiv.getBoundingClientRect();
    svgXValue.value=evt.clientX-rect.left
    svgYValue.value=evt.clientY-rect.top
}
</script>
<script>
document.addEventListener("onload",init(),false)
function init()
{
    initTransforms()
    svgSourceValue.value=svgDiv.innerHTML
    jsValue.value=myScript.text
}
</script>

</body>

</html>