【问题标题】:How do you transform event coordinates to SVG coordinates despite bogus getBoundingClientRect()?尽管 getBoundingClientRect() 是假的,如何将事件坐标转换为 SVG 坐标?
【发布时间】:2014-01-06 19:23:51
【问题描述】:

我正在尝试根据鼠标的位置在 SVG 元素上动态绘制内容。不幸的是,我很难将鼠标坐标从 mousemove 事件转换到 SVG 元素的坐标空间。

这是我一直在测试的一个有问题的函数:

CylinderDemo.prototype.handleMouseMove = function(evt)
{
    debugEvent = evt;

    var bcr = evt.target.getBoundingClientRect();
    var x2 = evt.clientX - bcr.left;
    var y2 = evt.clientY - bcr.top;

    console.log(evt.target);
    //console.log(evt.clientY+" - "+evt.target.getBBox());

    var d = this.pathForCylinder(this.x0, this.y0, x2, y2, 30);
    this.cap.setAttributeNS(null, "d", d);
}

canvas.onmousemove = function(evt) {
self.handleMouseMove(evt);
}

问题是(至少在 Firefox 中)getBoundingClientRect() 没有给我 SVG 对象的边界。它为我提供了 SVG 对象内的可绘制对象的边界。当您将鼠标悬停在 firebug 中的日志行上时,它会变得非常明显,它会突出显示可绘制元素的微不足道的子矩形。这意味着我的坐标变换给出了有缺陷的结果。

我应该使用什么技术从事件坐标系转换到 SVG 容器的坐标系?

我只是拼凑了http://jsfiddle.net/7kvkq/ 来说明问题。

【问题讨论】:

  • 重新生成路径命令以偏移路径非常麻烦。相反,我建议对路径应用转换(您可以通过 DOM0 setAttribute('transform',theRightString) 或通过使用 SVG DOM 创建/修改专为拖动而设计的转换来实现。

标签: javascript svg


【解决方案1】:

不要使用getBoundingClientRect()。相反,使用getScreenCTM() 将点从屏幕空间转换为全局 SVG 空间:

var pt = demo.createSVGPoint(); // demo is an SVGElement
demo.addEventListener('mousemove',function(evt) {
  pt.x = evt.clientX;
  pt.y = evt.clientY;
  var svgGlobal = pt.matrixTransform(demo.getScreenCTM().inverse());
  // svgGlobal.x and svgGlobal.y are now in SVG coordinates
},false);

固定演示:http://jsfiddle.net/7kvkq/3/

如果您需要将元素从屏幕空间转换为本地转换,请使用getTransformToElement() 进一步转换该点:

var elTransform = demo.getTransformToElement(someElement);
var elLocal     = svgGlobal.matrixTransform(elTransform );

getTransformToElement()的演示:http://jsfiddle.net/7kvkq/4/

为了获得更好的性能,不要将点转换两次并创建中间点,而是将矩阵合并为一个并使用它来转换坐标:

var demo = document.querySelector('svg'),
    pt   = demo.createSVGPoint(),
    g    = demo.querySelector('#myGroup');

// Assumes that the group does not move with respect to the SVG;
// if so, re-calculate this as appropriate.
var groupXForm = demo.getTransformToElement(g);
demo.addEventListener('mousemove',function(evt) {
    pt.x = evt.clientX;
    pt.y = evt.clientY;
    var xform = groupXForm.multiply(demo.getScreenCTM().inverse());
    var localPoint = pt.matrixTransform(xform);
    // localPoint.x/localPoint.y are the equivalent of your mouse position
},false);

您可以在我的网站上查看使用这些技术的演示:
http://phrogz.net/svg/drag_under_transformation.xhtml

【讨论】:

    【解决方案2】:

    事件目标可能是也可能不是 SVG 容器。请参阅MDN Documentation。如果要获取容器的边界框,直接在容器上调用getBoundingClientRect即可。我在这里叉了你的小提琴:

    http://jsfiddle.net/4RF75/1/

    另外,如果可以确定 SVG 元素不会改变大小,缓存边界框可能是个好主意,因为(尤其是在 WebKit 浏览器上)getBoundingClientRect 会触发布局,这可能太昂贵了在事件处理程序中执行。

    【讨论】:

    • 你的叉子似乎仍然有同样的问题。当您将鼠标移到 SVG 上时,红色圆圈会跳来跳去,并且永远不会在光标下方。
    • 看起来这可能是一个 Firefox 错误。缓存边界框有效,我编辑了小提琴以包含它。原始小提琴适用于除 Firefox 以外的所有浏览器。
    猜你喜欢
    • 2021-05-29
    • 2020-08-24
    • 2018-06-28
    • 2013-09-22
    • 2018-05-01
    • 2012-12-02
    • 1970-01-01
    • 1970-01-01
    • 2016-11-04
    相关资源
    最近更新 更多