Положение мыши внутри автомасштабируемого SVG

Я испытываю проблемы относительно положения курсора мыши внутри моего документа SVG. Я хотел бы создать потенциометр, который будет следовать за курсором при перетаскивании, используя JavaScript на странице HTML.

Я попробовал evt.clientX/Y и evt.screenX/Y, но поскольку мой SVG находится в автомасштабе, координаты внутри моего SVG отличаются. Я искал ответ в течение нескольких дней, но я не мог найти никакого решения (либо зная мой коэффициент масштабирования SVG в реальном времени, либо имею функцию для размещения мыши в системе координат SVG).

Вращение будет следовать простому правилу:

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;

С (xc; yc) в качестве центра вращения и заменяя все evt.screenX/Y координатами мыши внутри моего SVG.

Ответ 1

Смотрите этот код, который не только показывает, как преобразовать из пространства экрана в глобальное пространство SVG, но и как преобразовать точку из пространства SVG в преобразованное пространство элемента:
http://phrogz.net/svg/drag_under_transformation.xhtml

Короче:

// 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);

Изменить. Я создал образец, соответствующий вашим потребностям (хотя и только в глобальном пространстве SVG):
http://phrogz.net/svg/rotate-to-point-at-cursor.svg

Он добавляет следующий метод:

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)+')'
  );
}

Ответ 2

@Phrogz: Спасибо за прекрасный пример, и я узнал об этом. Я изменил некоторые из них, как показано ниже, чтобы сделать это немного легко. Поскольку я думаю, что, как мы обрабатываем события мыши в ядре Java, мы также можем обрабатывать то же самое здесь, поэтому я пробовал свой путь в вашем примере.

Я удалил функцию rotateElement, поскольку я думаю, что это сложно, и я нахожу замену, если она.

См. ниже код:

var svg=document.getElementById("svg1");
var pt=svg.createSVGPoint();
var end_small=document.getElementById("end_small");
var line=document.getElementById("line1");

end_small.addEventListener('mousemove', function(evt) {

    var loc=getCursor(evt);
    end_small.setAttribute("cx",loc.x);
    end_small.setAttribute("cy",loc.y);

    loc = getCursor(evt); // will get each x,y for mouse move

    line.setAttribute('x2',loc.x); // apply it  as end points of line
    line.setAttribute('y2',loc.y); // apply it as end points of line

}, false);

function getCursor(evt) {
    pt.x=evt.clientX;
    pt.y=evt.clientY;
    return pt.matrixTransform(svg.getScreenCTM().inverse());
}

Итак, что я сделал, я просто добавил слушателя только к маленькому кругу, а не к целому SVG, и каждый раз, когда мышь двигалась вами, я получаю функцию x, y от getCursor(), как указано выше, и я дам это x, y как x2, y2 моей строки, которая не преобразуется и не вращается. Вы должны перемещать мышь в круг, а затем медленно перемещаться, и если ваша мышь покидает круг, линия не будет двигаться, поскольку мы только что добавили слушателя только на маленьком круге вправо.

Ответ 3

Получение правильной координаты мыши svg сложнее. Прежде всего, общий способ заключается в том, чтобы использовать clientX и clientY свойства события, выдать его с помощью getBoundingClientRect() и clientLeft соответственно 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;
}

Но, если у svg есть информация стиля заполнения, отличная от нуля, координата смещается. Поэтому эта информация также должна быть вычтенной:

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;

И не очень приятно думать, что в некоторых браузерах свойство границы также сдвигает координату, а в другом нет. Я узнал, что сдвиг происходит, если х и у свойства события недоступны.

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

После этого преобразования координаты x и y могут быть не связаны, что должно быть исправлено. Но это не мысль.

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

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

Это решение может использоваться для кликов, mousemove, mousedown,... и так далее. Вы можете найти демо-версию здесь: https://codepen.io/martinwantke/pen/xpGpZB