问题是如何在将点(x,y)
显示在常规图形SVG元素(例如rect
,ellipse
,path
)内之前,先将其识别为-屏幕。这意味着无法获得在屏幕坐标中获得位置的任何方法,只能使用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
)。
仍然,代码中的数学越多,越有趣。
答案 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元素的绕线数并不简单,对于更复杂的形状,它可能会断裂。尽管如此,由于形状在我们的用例中非常简单,因此该解决方案可以。