1

I'm building an application in which I'm hoping to use SVG and interact.js together for drag and drop of SVG elements. My SVG is using a ViewBox in order to scale the graphics correctly on different resolutions, but the issue is that when I drag, the mouse moves "faster" than the element its moving.

I understand that is is an issue between the coordinate system of the screen and the SVG, and I've found what appears to be an answer here: Interact JS, Drag svg elements inside viewboxed svg?

However, I can't figure out how to tie the marked answer at that link above into my code. Every different thing I try results in even more bizarre behavior. I'm trying to integrate it into the drag and drop example provided on the interact.js site:

interact('.draggable')
    .draggable({
        // enable inertial throwing
        inertia: true,
        // keep the element within the area of it's parent
        restrict: {
            restriction: "parent",
            endOnly: true,
            elementRect: { top: 0, left: 0, bottom: 1, right: 1 }
        },
        // enable autoScroll
        autoScroll: true,

        // call this function on every dragmove event
        onmove: dragMoveListener,
        // call this function on every dragend event
        onend: function (event) {
            //removed this code for my test
        }
    });

function dragMoveListener (event) {
    var target = event.target,
        // keep the dragged position in the data-x/data-y attributes
        x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx,
        y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;

    // translate the element
    target.style.webkitTransform =
    target.style.transform =
          'translate(' + x + 'px, ' + y + 'px)';

    // update the posiion attributes
    target.setAttribute('data-x', x);
    target.setAttribute('data-y', y);
}

The closest I've managed to get is with this code in place of the dragMoveListener above:

function dragMoveListener(event) {
    var target = event.target;

    var svg = document.querySelector('svg');
    var svgRect = svg.getBoundingClientRect();
    var ctm = target.getScreenCTM();
    var point = svg.createSVGPoint();
    var point2;

    point.x = event.clientX;
    point.y = event.clientY;

    point2 = point.matrixTransform(ctm);

    var x = point2.x;
    var y = point2.y;

    // translate the element
    target.style.webkitTransform =
        target.style.transform =
        'translate(' + x + 'px, ' + y + 'px)';

    // update the posiion attributes
    target.setAttribute('data-x', x);
    target.setAttribute('data-y', y);
}

But this causes the element to be very far from the mouse, though it moves almost in sync (seems to be a bit off still). How can I get this to work, and what am I possibly misunderstanding about the solution presented at the linked question based on my "almost" solution above?

Here is the relevant HTML. All of the settings are just for testing purposes and may not be final (e.g. 2000 and 4000 are likely larger than I will ultimately need).

<svg id="svgArea" style="width:100%; border: 1px solid black" viewBox="0 0 2000 4000">
    <rect id="item" width="100" height="200" stroke="#000000" stroke-width="5" fill="#ff0000" class="draggable" ></rect>
</svg>
matthew_b
  • 739
  • 1
  • 7
  • 18
  • Maybe a look at this [d3.js function](https://github.com/d3/d3-selection/blob/master/src/point.js) can help. It takes a locatable event and a element node and returns the coordinates of that event in the local coordinate system of the element. – ccprog Dec 01 '17 at 20:29
  • so the issue is that you are not using inversed matrix (CTM). I will get you a link with implementation – Sergey Rudenko Dec 01 '17 at 22:49
  • also not sure why would you want to use CSS transform - its better to use SVG transform. See the code example I shared. Its rotation there, but its fairly easy to do drag and drop in same way. – Sergey Rudenko Dec 01 '17 at 22:56

1 Answers1

3

here is the working snippet where drag rotate is using proper SVG to DOM coordinates conversion:

Codepen drag rotate snippet

And this in particular what you are missing:

  mouse.x = event.clientX;
  mouse.y = event.clientY;
  mouse = mouse.matrixTransform(mainSVG.getScreenCTM().inverse());

So in your case you should getCTM from svgArea element.

Sergey Rudenko
  • 8,809
  • 2
  • 24
  • 51
  • 1
    Thanks! I tried the using `inverse()`, but it never worked for me. Looks like the key was that I was not getting the correct CTM (was getting it for the item I was dragging rather than the SVG as a whole). – matthew_b Dec 04 '17 at 13:14