How is the getBBox() SVGRect calculated?
我有一个
使用两个矩形之间的差异(
这是问题所在–完成转换后,我将更新
BTW是在Firefox 4中运行的HTML 5文档中,如果有区别的话。
更新:显然,这种行为似乎很明显地违反了规范。来自w3c处的文本:
SVGRect getBBox()
Returns the tight bounding box in current user space (i.e., after application of the a€?transforma€? attribute, if any) on the geometry of all contained graphics elements, exclusive of stroking, clipping, masking and filter effects). Note that getBBox must return the actual bounding box at the time the method was called, even in case the element has not yet been rendered.
我阅读正确吗?如果是这样,这似乎是Firefox使用的SVG实现中的勘误表;我还没有机会尝试其他任何方法。如果有人可以指出我的位置,我将提交错误报告。
人们经常会因getBBox和getBoundingClientRect的行为差异而感到困惑。
getBBox是SVG元素的本机方法,等效于查找HTML DOM元素的偏移量/客户端宽度。即使旋转元素,宽度和高度也永远不会改变。它不能用于HTML DOM元素。
getBoundingClientRect对HTML和SVG元素都是通用的。当旋转元素或将更多元素分组时,有界矩形的宽度和高度将发生变化。
您看到的行为是正确的,并且符合规范。
应用转换,然后以"当前用户单位"(即当前用户空间)计算bbox。因此,如果要查看元素上转换的结果,则需要查看父节点或类似节点的bbox。
这有点令人困惑,但是在SVGocatable的SVG Tiny 1.2规范中解释得更好
其中包含许多示例,阐明了其应做的事情。
至少有2种简单但有点怪异的方式来完成您要问的事情...如果有更好(更怪异)的方式,我还没有找到它们
轻松暴躁#1:
a)设置一个与group.getBBox()返回的"未转换" bbox匹配的矩形
b)将组的"未应用的变换"应用于该矩形
c)rect.getBBox()现在应该返回您正在寻找的bbox
EASY HACKY#2 :(仅适用于chrome测试)
a)使用element.getBoundingClientRect(),它返回足够的信息供您构造要查找的bbox
显然,getBBox()并未考虑转换。
很遗憾,我无法将您指向此处:http://tech.groups.yahoo.com/group/svg-developers/message/22891
SVG小组有讨厌的作法-不要累积所做的所有转换。我有办法解决这个问题。我正在使用自己的属性来存储当前的转换数据,这些数据将包含在任何进一步的转换中。使用与XML兼容的属性(例如alttext,value,name ...),或者仅使用x和y来将累积值存储为属性。
示例:
1 2 3 | <g id="group" x="20" y="100" transform="translate(20, 100)"> <g id="subgroup" alttext="45" transform="rotate(45)"> <line...etc... |
因此,当我进行转换时,我要获取那些手工制作的属性值,而当将其写回时,我将同时使用我为保留所有累加值而创建的属性同时写入转换和同一个值。
旋转示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | function symbRot(evt) { evt.target.ondblclick = function () { stopBlur(); var ptx=symbG.parentNode.lastChild.getAttribute("cx"); var pty=symbG.parentNode.lastChild.getAttribute("cy"); var currRot=symbG.getAttributeNS(null,"alttext"); var rotAng; if (currRot == 0) { rotAng = 90 } else if (currRot == 90) { rotAng = 180 } else if (currRot == 180) { rotAng = 270 } else if (currRot == 270) { rotAng = 0 }; symbG.setAttributeNS(null,"transform","rotate(" + rotAng +"," + ptx +"," + pty +")"); symbG.setAttributeNS(null,"alttext", rotAng ); }; } |
我创建了一个辅助函数,该函数返回svg元素(以及转换后的元素的bbox)的各种度量。
代码在这里:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | SVGElement.prototype.getTransformToElement = SVGElement.prototype.getTransformToElement || function(elem) { return elem.getScreenCTM().inverse().multiply(this.getScreenCTM()); }; function get_metrics(el) { function pointToLineDist(A, B, P) { var nL = Math.sqrt((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y)); return Math.abs((P.x - A.x) * (B.y - A.y) - (P.y - A.y) * (B.x - A.x)) / nL; } function dist(point1, point2) { var xs = 0, ys = 0; xs = point2.x - point1.x; xs = xs * xs; ys = point2.y - point1.y; ys = ys * ys; return Math.sqrt(xs + ys); } var b = el.getBBox(), objDOM = el, svgDOM = objDOM.ownerSVGElement; // Get the local to global matrix var matrix = svgDOM.getTransformToElement(objDOM).inverse(), oldp = [[b.x, b.y], [b.x + b.width, b.y], [b.x + b.width, b.y + b.height], [b.x, b.y + b.height]], pt, newp = [], obj = {}, i, pos = Number.POSITIVE_INFINITY, neg = Number.NEGATIVE_INFINITY, minX = pos, minY = pos, maxX = neg, maxY = neg; for (i = 0; i < 4; i++) { pt = svgDOM.createSVGPoint(); pt.x = oldp[i][0]; pt.y = oldp[i][1]; newp[i] = pt.matrixTransform(matrix); if (newp[i].x < minX) minX = newp[i].x; if (newp[i].y < minY) minY = newp[i].y; if (newp[i].x > maxX) maxX = newp[i].x; if (newp[i].y > maxY) maxY = newp[i].y; } // The next refers to the transformed object itself, not bbox // newp[0] - newp[3] are the transformed object's corner // points in clockwise order starting from top left corner obj.newp = newp; // array of corner points obj.width = pointToLineDist(newp[1], newp[2], newp[0]) || 0; obj.height = pointToLineDist(newp[2], newp[3], newp[0]) || 0; obj.toplen = dist(newp[0], newp[1]); obj.rightlen = dist(newp[1], newp[2]); obj.bottomlen = dist(newp[2], newp[3]); obj.leftlen = dist(newp[3], newp[0]); // The next refers to the transformed object's bounding box obj.BBx = minX; obj.BBy = minY; obj.BBx2 = maxX; obj.BBy2 = maxY; obj.BBwidth = maxX - minX; obj.BBheight = maxY - minY; return obj; } |
和完整的功能示例在这里:
http://jsbin.com/acowaq/1
以下代码考虑了来自父母,本身以及孩子的转换(矩阵或其他方式)。因此,它将在例如
您通常会希望将父
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | /** * @param {SVGElement} element - Element to get the bounding box for * @param {boolean} [withoutTransforms=false] - If true, transforms will not be calculated * @param {SVGElement} [toElement] - Element to calculate bounding box relative to * @returns {SVGRect} Coordinates and dimensions of the real bounding box */ function getBBox(element, withoutTransforms, toElement) { var svg = element.ownerSVGElement; if (!svg) { return { x: 0, y: 0, cx: 0, cy: 0, width: 0, height: 0 }; } var r = element.getBBox(); if (withoutTransforms) { return { x: r.x, y: r.y, width: r.width, height: r.height, cx: r.x + r.width / 2, cy: r.y + r.height / 2 }; } var p = svg.createSVGPoint(); var matrix = (toElement || svg).getScreenCTM().inverse().multiply(element.getScreenCTM()); p.x = r.x; p.y = r.y; var a = p.matrixTransform(matrix); p.x = r.x + r.width; p.y = r.y; var b = p.matrixTransform(matrix); p.x = r.x + r.width; p.y = r.y + r.height; var c = p.matrixTransform(matrix); p.x = r.x; p.y = r.y + r.height; var d = p.matrixTransform(matrix); var minX = Math.min(a.x, b.x, c.x, d.x); var maxX = Math.max(a.x, b.x, c.x, d.x); var minY = Math.min(a.y, b.y, c.y, d.y); var maxY = Math.max(a.y, b.y, c.y, d.y); var width = maxX - minX; var height = maxY - minY; return { x: minX, y: minY, width: width, height: height, cx: minX + width / 2, cy: minY + height / 2 }; } |