【问题标题】:Mouse position inside autoscaled SVG自动缩放 SVG 内的鼠标位置
【发布时间】:2012-05-05 03:12:44
【问题描述】:

我在 SVG 文档中遇到了关于鼠标光标位置的问题。我想设计一个电位器,在拖动时跟随光标,在 HTML 页面中使用 JavaScript

我尝试了evt.clientX/Yevt.screenX/Y,但由于我的 SVG 是在 autoscale 中,我的 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 中鼠标的坐标。

【问题讨论】:

  • 您必须使用变换矩阵才能获得正确的坐标。一个 jsfiddle 会很有帮助。

标签: javascript svg


【解决方案1】:

获取正确的 svg 鼠标坐标很棘手。首先,一种常见的方法是使用 event 属性的 clientX 和 clientY 分别用 getBoundingClientRect() 和 clientLeft 减去它。

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;

不太好的想法是,在某些浏览器中,边框属性也会移动坐标,而在其他浏览器中则不会。我发现,如果事件属性的 x 和 y 不可用,就会发生转移。

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;
}

此解决方案可用于 click、mousemove、mousedown 等。 您可以在这里进行现场演示:https://codepen.io/martinwantke/pen/xpGpZB

【讨论】:

    【解决方案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 并且每次当你移动鼠标时,我都会从上面提到的 getCursor() 函数中得到 x, y,我会给这个 x, y作为我的行的x2, y2,它不会翻译也不会旋转。您必须将鼠标移动到圆圈,然后缓慢移动,如果您的鼠标离开圆圈,则线不会移动,因为我们刚刚在小圆圈右侧添加了侦听器。

    【讨论】:

      【解决方案3】:

      看这段代码,它不仅展示了如何从屏幕空间变换到全局 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)+')'
        );
      }
      

      【讨论】:

      • 嗨,对不起,我没有早点回复,事实是我不能,使用 Firefox(我不知道为什么)。所以我看到你的代码是分开工作的,但我仍然在处理一些奇怪的问题,一旦我找到让它在我的项目中工作的方法,我会尽快回复你!谢谢
      • 我的 getBBox() 似乎没有正常工作,所以我无法获得您使用的正确原点(我需要它是自动的,以便可以轻松移动元素使用inkscape,甚至复制/粘贴到另一个svg)。我一直在尝试很多东西,但没有任何效果。
      • @Riwall 我建议您为您的 BBox 问题提出一个新问题;请务必包含一个简单的、精简的测试用例(在问题或 JSFiddle 中的代码中),它可以重现您的问题,并描述给您带来麻烦的操作系统/浏览器/版本。
      • 我知道这不应该写成评论,而是:“非常感谢!”。几个星期以来,我一直在为类似的问题苦苦挣扎,直到找到您的答案。现在我的代码完美运行!
      • @osvein 当文档是 SVG 文件时是这样,但当文档是可能包含 SVG 文件的 HTML 时则不然。请注意,如果您只需要 createSVGPoint() 的 SVG 元素,您也可以 svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
      猜你喜欢
      • 2020-05-28
      • 2018-10-18
      • 1970-01-01
      • 1970-01-01
      • 2014-08-02
      • 1970-01-01
      • 1970-01-01
      • 2013-03-26
      • 2015-04-20
      相关资源
      最近更新 更多