2

In the following code, I have working zoom and dragging of the canvas. Also - when click on canvas I can add blue circles. I can also move blue circles around - but the problem is that circle center is moved to the mouse pointer position, not taking into account offset from mouse pointer to center of the circle. How can I include this offset in calculation that would work even when the canvas is scaled/repositioned?

var svgCanvas = document.getElementById("canvas");
var viewPort = document.getElementById("viewport");
var drag = false;
var dragged = false;
var offset = { x: 0, y: 0 };
var factor = .1;
var matrix = new DOMMatrix();
var whatToMove = function (event) { };
svgCanvas.addEventListener('pointerdown', function (event) {
    drag = true;
    whatToMove = moveViewPort;
    offset = { x: event.clientX, y: event.clientY };
});
document.addEventListener('pointermove', function (event) {
    if (event.buttons == 1) {
        whatToMove(event);
    }
});
var moveViewPort = function (event) {
    if (drag) {
        var tx = event.clientX - offset.x;
        var ty = event.clientY - offset.y;
        offset = {
            x: event.clientX,
            y: event.clientY
        };
        matrix.preMultiplySelf(new DOMMatrix()
            .translateSelf(tx, ty));
        viewPort.style.transform = matrix.toString();
        dragged = true;
    }
};
svgCanvas.addEventListener('pointerup', function (event) {
    if (!dragged) {
        var c = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
        var point = new DOMPoint(event.offsetX, event.offsetY);
        var tPoint = matrix.inverse().transformPoint(point);
        c.cx.baseVal.value = tPoint.x;
        c.cy.baseVal.value = tPoint.y;
        c.r.baseVal.value = 10;
        c.style.fill = "blue";
        var cdrag = false;
        c.addEventListener('pointerdown', function (event) {
            event.stopPropagation();
            cdrag = true;
            whatToMove = moveCircle;
        });
        var moveCircle = function (event) {
            if (cdrag) {
                var rect = svgCanvas.getBoundingClientRect();
                var x = event.clientX - rect.left;
                var y = event.clientY - rect.top;
                var point_1 = new DOMPoint(x, y);
                var tPoint_1 = matrix.inverse().transformPoint(point_1);
//!!!!!!! HERE NEEDS TO INCLUDE ADDITONAL OFFSET FROM POINTER TO CENTER OF CIRCLE
                c.cx.baseVal.value = tPoint_1.x;
                c.cy.baseVal.value = tPoint_1.y;
            }
        };
        c.addEventListener('pointerup', function (event) {
            event.stopPropagation();
            cdrag = false;
            whatToMove = function () { };
        });
        viewPort.appendChild(c);
    }
    drag = false;
    whatToMove = function () { };
    dragged = false;
});
document.addEventListener('wheel', function (event) {
    event.preventDefault();
    var zoom = event.deltaY > 0 ? -1 : 1;
    var scale = 1 + factor * zoom;
    offset = {
        x: event.offsetX,
        y: event.offsetY
    };
    matrix.preMultiplySelf(new DOMMatrix()
        .translateSelf(offset.x, offset.y)
        .scaleSelf(scale, scale)
        .translateSelf(-offset.x, -offset.y));
    viewPort.style.transform = matrix.toString();
}, { passive:false });
      #around{
          display: flex;
          width: 100%;
          height: 400px;
          padding: 20px;
          border: 1px dashed orange;
      }

      #canvas{
          flex: 1;
          height: auto;
      }
<div id="around">

<svg id="canvas" style="border: 1px solid blue;">
  <g id="viewport">
    <rect x="100" y="100" width="400" height="200" fill="red"/>
    <circle r="10" cx="600" cy="600" fill="blue"/>
  </g>
</svg>

</div>

1 Answer 1

3

You need to store the offset between the mouse cursor and the center of the circle when the drag event begins.

You have already devised a way to get the mouse position in the right referential:

var rect = svgCanvas.getBoundingClientRect();
var x = event.clientX - rect.left;
var y = event.clientY - rect.top;
var point_1 = new DOMPoint(x, y);
var tPoint_1 = matrix.inverse().transformPoint(point_1);

Use the same code to compute the initial offset and store it (startDragOffset) on pointerdown:

startDragOffset.x = c.cx.baseVal.value - tPoint_1.x;
startDragOffset.y = c.cy.baseVal.value - tPoint_1.y;

Finally add that offset to the mouse position in moveCircle:

c.cx.baseVal.value = startDragOffset.x + tPoint_1.x;
c.cy.baseVal.value = startDragOffset.y + tPoint_1.y;

Demo:

var svgCanvas = document.getElementById("canvas");
var viewPort = document.getElementById("viewport");
var drag = false;
var dragged = false;
var offset = { x: 0, y: 0 };
var factor = .1;
var matrix = new DOMMatrix();
var whatToMove = function (event) { };
var startDragOffset = { x: 0, y: 0 };
svgCanvas.addEventListener('pointerdown', function (event) {
    drag = true;
    whatToMove = moveViewPort;
    offset = { x: event.clientX, y: event.clientY };
});
document.addEventListener('pointermove', function (event) {
    if (event.buttons == 1) {
        whatToMove(event);
    }
});
var moveViewPort = function (event) {
    if (drag) {
        var tx = event.clientX - offset.x;
        var ty = event.clientY - offset.y;
        offset = {
            x: event.clientX,
            y: event.clientY
        };
        matrix.preMultiplySelf(new DOMMatrix()
            .translateSelf(tx, ty));
        viewPort.style.transform = matrix.toString();
        dragged = true;
    }
};
svgCanvas.addEventListener('pointerup', function (event) {
    if (!dragged) {
        var c = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
        var point = new DOMPoint(event.offsetX, event.offsetY);
        var tPoint = matrix.inverse().transformPoint(point);
        c.cx.baseVal.value = tPoint.x;
        c.cy.baseVal.value = tPoint.y;
        c.r.baseVal.value = 10;
        c.style.fill = "blue";
        var cdrag = false;
        c.addEventListener('pointerdown', function (event) {
            event.stopPropagation();
            cdrag = true;
            whatToMove = moveCircle;
            
            var rect = svgCanvas.getBoundingClientRect();
            var x = event.clientX - rect.left;
            var y = event.clientY - rect.top;
            var point_1 = new DOMPoint(x, y);
            var tPoint_1 = matrix.inverse().transformPoint(point_1);

            startDragOffset.x = c.cx.baseVal.value - tPoint_1.x;
            startDragOffset.y = c.cy.baseVal.value - tPoint_1.y;
        });
        var moveCircle = function (event) {
            if (cdrag) {
                var rect = svgCanvas.getBoundingClientRect();
                var x = event.clientX - rect.left;
                var y = event.clientY - rect.top;
                var point_1 = new DOMPoint(x, y);
                var tPoint_1 = matrix.inverse().transformPoint(point_1);
//!!!!!!! HERE NEEDS TO INCLUDE ADDITONAL OFFSET FROM POINTER TO CENTER OF CIRCLE
                c.cx.baseVal.value = startDragOffset.x + tPoint_1.x;
                c.cy.baseVal.value = startDragOffset.y + tPoint_1.y;
            }
        };
        c.addEventListener('pointerup', function (event) {
            event.stopPropagation();
            cdrag = false;
            whatToMove = function () { };
        });
        viewPort.appendChild(c);
    }
    drag = false;
    whatToMove = function () { };
    dragged = false;
});
document.addEventListener('wheel', function (event) {
    var zoom = event.deltaY > 0 ? -1 : 1;
    var scale = 1 + factor * zoom;
    offset = {
        x: event.offsetX,
        y: event.offsetY
    };
    matrix.preMultiplySelf(new DOMMatrix()
        .translateSelf(offset.x, offset.y)
        .scaleSelf(scale, scale)
        .translateSelf(-offset.x, -offset.y));
    viewPort.style.transform = matrix.toString();
});
      #around{
          display: flex;
          width: 100%;
          height: 400px;
          padding: 20px;
          border: 1px dashed orange;
      }

      #canvas{
          flex: 1;
          height: auto;
      }
<div id="around">

<svg id="canvas" style="border: 1px solid blue;">
  <g id="viewport">
    <rect x="100" y="100" width="400" height="200" fill="red"/>
    <circle r="10" cx="600" cy="600" fill="blue"/>
  </g>
</svg>

</div>

Of course, you'd like to make adjustments, like factoring away the duplicated code that converts the mouse position.

1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.