如何找到一个点是否在已解析但未显示的SVG文件的SVG元素内?

时间:2018-10-31 08:04:21

标签: javascript html svg

问题是如何在将点(x,y)显示在常规图形SVG元素(例如rectellipsepath)内之前,先将其识别为-屏幕。这意味着无法获得在屏幕坐标中获得位置的任何方法,只能使用SVG文件中给出的数据。

背景是我的公司需要一个用于从Adobe Illustrator标记SVG元素的系统,并且由于AI不允许进行太多SVG输出修改,因此我们决定基于此创建单独的层tags包含标签文本的文本对象。然后,在客户端,将解析文件并将标签添加到相应的SVG元素,然后再删除tags组。

获得标签的基本坐标很容易(使用ES5-抱歉)。对于给定的svg字符串:

var parser = new DOMParser();

var svgDom = parser.parseFromString(svg, "image/svg+xml");
var tagsElement = svgDom.getElementById("tags");

if (tagsElement) {
    var textElementHtmlCollection = tagsElement.getElementsByTagName("text");
    var textElementArray = Array.prototype.slice.call(textElementHtmlCollection);
    var tagDataArray = textElementArray.map(function (el) {
        return {
            text: el.textContent,
            pos: {
                x: el.transform.baseVal[0].matrix.e,
                y: el.transform.baseVal[0].matrix.f
            }
        };
    });

现在,标记系统的一个非常基本的版本正在运行,但是每个节点类型都必须单独处理,而我甚至为{实施了一种改进的数字绕数算法(https://en.wikipedia.org/wiki/Winding_number) {1}}个节点(“关闭调用”案例所引起的问题不属于该用例):

path

现在,我已经决定必须采用另一种方法,因为考虑到可能将各种function windingNumber (element,x,y) { var isInside, p1 = undefined, p2 = undefined, px, py, R, wn, sum=0; var totalLength = element.getTotalLength(); var resolution = 5; var threshold = 0.1; for (var len = 0; len <= totalLength; len += resolution) { p1 = p2; p2 = element.getPointAtLength(len); if (p1) { px = p1.x - x; py = p1.y - y; R = px*px + py*py; sum += (px*(p2.y-p1.y)/R - py*(p2.x-p1.x)/R); } wn = sum/(2*Math.PI); if (Math.abs(wn) > 1-threshold) { isInside = true; break; } } return isInside; } 应用于元素时,系统变得非常复杂。

有没有更简单的方法来实现这一目标?也欢迎提出不同方法的想法。

更新我的回答很糟糕,但这是我最终使用的答案。应用程序周围的基础架构和问题使得真正难以获得“适当的”解决方案(隐藏SVG,使用transform)。

仍然,代码中的数学越多,越有趣。

2 个答案:

答案 0 :(得分:1)

罗伯特的建议应该是最简单的方法。

您应该只能够将测试元素附加到SVG,使用elementFromPoint()检查点是否结束,然后再次删除该元素。

function testPointInElement(elem, x, y) {
  svg.appendChild(elem);
  var hitElem = document.elementFromPoint(x,y);
  // If you remove it again before the browser refreshes, it should not ever be visible
  svg.removeChild(elem);
  return hitElem === elem;
}

var svg = document.getElementById("mysvg");

// Generates a sample test element
function sampleElement() {
  var path = document.createElementNS("http://www.w3.org/2000/svg", "path");
  path.setAttribute("d", "M 200,300 L 300,100 L 350,350 L 200,200 L 400,200 Z");
  path.setAttribute("fill-rule", "evenodd");
  return path;
}

function testPointInElement(elem, x, y) {
  svg.appendChild(elem);
  var hitElem = document.elementFromPoint(x,y);
  // If you remove it again before the browser refreshes, it should not ever be visible
  svg.removeChild(elem);
  return hitElem === elem;
}



var testElem = sampleElement();

svg.addEventListener("click", function(evt) {
  var x = evt.clientX;
  var y = evt.clientY;
  
  // Find out if any element is at x,y
  if (testPointInElement(testElem, x,y)) {
    alert("Hit!");
  }
});
svg {
  background-color: linen;
}
<svg id="mysvg" width="400" height="400">
  <!-- ghost path showing where our test path is -->
  <path d="M 200,300 L 300,100 L 350,350 L 200,200 L 400,200 Z" fill-rule="evenodd" opacity="0.1"/>
</svg>

在SVG2中,它甚至更容易。您将可以在元素上调用isPointInFill()。但是浏览器对此的支持不佳。ATM。

答案 1 :(得分:0)

我这样做的最终方法是将元素的逆变换应用于我尝试找到的点。

// Instead of taking into account transformations on the element,
// invert the transformations and apply them to the point.
if (element.transform && element.transform.baseVal) {
    // Get the transformation matrix information.
    var transform = element.transform.baseVal.consolidate();
    if (transform) {
        // Calculate the inverse of the transformation matrix.
        var transformMatrix = transform.matrix;
        var inverseTM = transformMatrix.inverse();

        // Calculate the new x and y values as a matrix multiplication of
        // the inverse transformation matrix and the vector (x,y,1).
        //
        //   a c e       x       a*x + c*y + e       newX
        // ( b d f ) * ( y ) = ( b*x + d*y + f ) = ( newY )
        //   0 0 1       1             1              1
        //
        var newX, newY;
        newX = inverseTM.a*x + inverseTM.c*y + inverseTM.e;
        newY = inverseTM.b*x + inverseTM.d*y + inverseTM.f;
        x = newX;
        y = newY;
    }
}

如果遇到任何问题,我会进行更新,但这似乎出奇地好。

谨防任何这样做的人:SVG元素的绕线数并不简单,对于更复杂的形状,它可能会断裂。尽管如此,由于形状在我们的用例中非常简单,因此该解决方案可以。