获取元素的边界框,说明其转换
Get bounding box of element accounting for its transform
给定此 SVG:
<svg xmlns="http://www.w3.org/2000/svg">
<rect width="50" height="50"
transform="translate(75,75) rotate(45) translate(-25,-25)" />
<script>
var bb = document.querySelector('rect').getBBox();
console.log([bb.x,bb,y,bb.width,bb.height]);
</script>
</svg>
生成的输出为 [0, 0, 50, 50]
。
所需的结果是 [39.645,39.645,70.711,70.711]
.
视觉版本:http://jsfiddle.net/2wpju/7/
计算元素相对于其父元素的边界框的最简单、最有效的方法是什么?
以下是我到目前为止想出的最佳答案,但有两个问题:
- 似乎有很多
- 工作可以更好地处理,并且
- 如本演示所示,它不一定是最小边界框(注意上面的圆圈(,因为旋转轴对齐边界框的轴对齐边界框总是会增加大小。
// Calculate the bounding box of an element with respect to its parent element
function transformedBoundingBox(el){
var bb = el.getBBox(),
svg = el.ownerSVGElement,
m = el.getTransformToElement(el.parentNode);
// Create an array of all four points for the original bounding box
var pts = [
svg.createSVGPoint(), svg.createSVGPoint(),
svg.createSVGPoint(), svg.createSVGPoint()
];
pts[0].x=bb.x; pts[0].y=bb.y;
pts[1].x=bb.x+bb.width; pts[1].y=bb.y;
pts[2].x=bb.x+bb.width; pts[2].y=bb.y+bb.height;
pts[3].x=bb.x; pts[3].y=bb.y+bb.height;
// Transform each into the space of the parent,
// and calculate the min/max points from that.
var xMin=Infinity,xMax=-Infinity,yMin=Infinity,yMax=-Infinity;
pts.forEach(function(pt){
pt = pt.matrixTransform(m);
xMin = Math.min(xMin,pt.x);
xMax = Math.max(xMax,pt.x);
yMin = Math.min(yMin,pt.y);
yMax = Math.max(yMax,pt.y);
});
// Update the bounding box with the new values
bb.x = xMin; bb.width = xMax-xMin;
bb.y = yMin; bb.height = yMax-yMin;
return bb;
}
你只需要使用这个简单的Javascript函数:
object.getBoundingClientRect();
它受以下支持:
Chrome Firefox (Gecko) Internet Explorer Opera Safari
1.0 3.0 (1.9) 4.0 (Yes) 4.0
查看此处以获取更多信息。链接
如果您不想处理数学,一个好的解决方法是将 SVG 元素包装在 <g>
组中,然后在新组上调用 getBBox()
。
这是它的伪代码
function getBBoxTakingTransformIntoAccount(element) {
let group = wrapInGroup(element)
let bBox = group.getBBox()
unwrapChild(group) // Optionally, you can unwrap the child node
return bBox
}
function wrapInGroup(element) {
let group = document.createElementNS("http://www.w3.org/2000/svg", "g")
element.parentNode.appendChild(group)
group.appendChild(element)
return group
}
function unwrapChild(element) {
let child = element.children[0]
element.parentNode.appendChild(child)
element.remove()
}
这似乎是当前的方法,除非/直到getBBox((认为对象上的转换直接定义了边界框,即使SVG 1.1 SE定义了:
SVGRect getBBox(( 返回当前用户空间中(即,在应用"transform"属性后,如果有的话(对所有包含的图形元素的几何图形的紧密边界框,不包括描边、剪切、遮罩和滤镜效果(。请注意,getBBox 必须在调用该方法时返回实际的边界框,即使元素尚未呈现也是如此。
我想这完全取决于您如何定义">当前用户空间">
这是一个解释旋转椭圆的答案。转换方法适用于矩形,但在椭圆上,如果旋转对象,则会返回一个比对象大得多的边界框。
使用内置JavaScript函数的简单答案对我来说不是很有用,因为它仍然需要转换回绘图空间。
此代码不处理椭圆的倾斜或缩放(但处理任何其他对象(,但在我 http://nwlink.com/~geoffrey/svgBuild 程序中,我打算通过绘制应用倾斜或缩放的全新椭圆来处理倾斜和缩放。
椭圆的公式来自这里:如何计算椭圆的轴对齐边界框? 非常感谢那里的帮助。
el 是 D3.js 元素,而不是节点。
function toNumberIfNumeric(x) {
var result = parseFloat(x);
return isNaN(result) ? x : result;
}
function getAttrs(el, attributes) {
var result = {};
attributes.forEach(function (attr) {
result[attr] = toNumberIfNumeric(el.attr(attr));
});
return result;
}
function getTransformedBbox(el, referenceNode) {
var node = el.node();
switch (node.nodeName) {
case "ellipse":
var rotate = getTransform(el, "rotate");
if (rotate.angle === 0) {
return node.getBBox();
}
var attr = getAttrs(el, ["cx", "cy", "rx", "ry"]);
var radians = rotate.angle / 180 * Math.PI;
var radians90 = radians + Math.PI / 2;
var ux = attr.rx * Math.cos(radians);
var uy = attr.rx * Math.sin(radians);
var vx = attr.ry * Math.cos(radians90);
var vy = attr.ry * Math.sin(radians90);
var halfWidth = Math.sqrt(ux * ux + vx * vx);
var halfHeight = Math.sqrt(uy * uy + vy * vy);
return {
x: attr.cx - halfWidth,
y: attr.cy - halfHeight,
width: 2 * halfWidth,
height: 2 * halfHeight
};
default:
var bb = node.getBBox(),
svg = node.ownerSVGElement,
m = node.getTransformToElement(referenceNode);
// Create an array of all four points for the original bounding box
var pts = [
svg.createSVGPoint(), svg.createSVGPoint(),
svg.createSVGPoint(), svg.createSVGPoint()
];
pts[0].x = bb.x;
pts[0].y = bb.y;
pts[1].x = bb.x + bb.width;
pts[1].y = bb.y;
pts[2].x = bb.x + bb.width;
pts[2].y = bb.y + bb.height;
pts[3].x = bb.x;
pts[3].y = bb.y + bb.height;
// Transform each into the space of the parent,
// and calculate the min/max points from that.
var xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity;
pts.forEach(function (pt) {
pt = pt.matrixTransform(m);
xMin = Math.min(xMin, pt.x);
xMax = Math.max(xMax, pt.x);
yMin = Math.min(yMin, pt.y);
yMax = Math.max(yMax, pt.y);
});
// Update the bounding box with the new values
bb.x = xMin;
bb.width = xMax - xMin;
bb.y = yMin;
bb.height = yMax - yMin;
return bb;
}
}
死灵法术。
所需的边界框坐标始终相对于要在其中使用坐标的元素的变换。
例如,您在
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2000 577">
<g id="test">
<path id="e123" d="m906.76 442.4h51.77v39.59h-51.77z" fill="#B3B3B3" fill-rule="evenodd" transform="matrix(1,0,0,0.999749,0,-75.2041)"/>
</g>
</svg>
现在您要添加另一个组
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2000 577">
<g id="test">
<path id="e123" d="m906.76 442.4h51.77v39.59h-51.77z" fill="#B3B3B3" fill-rule="evenodd" transform="matrix(1,0,0,0.999749,0,-75.2041)"/>
</g>
<g id="e456" data-group-id="AP">
<rect class="BG" fill="yellow" x="906.760009765625" y="390.5399169921875" width="15.96875" height="16.125"/>
<text id="foobar" fill="#000" font-size="1.5mm" data-lines-count="4" x="906.760009765625" y="390.5399169921875">
<tspan x="906.760009765625" dy="1.2em">.</tspan>
<tspan x="906.760009765625" dy="1.2em">ABC123</tspan>
<tspan x="906.760009765625" dy="1.2em">Test</tspan>
<tspan x="906.760009765625" dy="1.2em">123</tspan>
</text>
</g>
</svg>
现在,您要将该新组中的文本元素坐标和矩形坐标放入id为"e123"(矩形(的元素的边界框中。
所以你拿
document.getElementById("e123").getBBox();
此边界框考虑了您在 e123 中的转换,也就是在应用"transform"属性之后。如果你把这些坐标放在 e123 之外(想象一下 e123 是一个组而不是一条路径(,那么坐标将被该变换关闭。
因此,您需要获取相关元素的边界框(fromSpace(,并将它们转换为适合目标元素的坐标,例如"e456"。
你获取fromSpace边界框的坐标,创建两个点(左上角,右下角(,将它们转换为目标空间,然后从这两个点计算新的边界框(请注意,当您仅将x2设置为bbox.width和y2设置为bbox.height时,它不起作用 - 您需要另一个点,然后在将矩阵应用于每个点后重新计算宽度和高度(。
在代码中:
if (!SVGElement.prototype.getTransformToElement)
{
SVGElement.prototype.getTransformToElement = function (elem)
{
return elem.getScreenCTM().inverse().multiply(this.getScreenCTM());
};
}
function boundingBoxRelativeToElement(fromSpace, toSpace, bbox)
{
bbox = bbox || fromSpace.getBBox();
// var m = fromSpace.transform.baseVal[0].matrix;
var m = fromSpace.getTransformToElement(toSpace);
var bbC = document.documentElement.createSVGPoint(); bbC.x = bbox.x; bbC.y = bbox.y;
var bbD = document.documentElement.createSVGPoint(); bbD.x = bbox.x + bbox.width; bbD.y = bbox.y + bbox.height;
// console.log("bbC", bbC, "bbD", bbD);
bbC = bbC.matrixTransform(m);
bbD = bbD.matrixTransform(m);
var bb = { x: bbC.x, y: bbC.y, width: Math.abs(bbD.x - bbC.x), height: Math.abs(bbD.y - bbC.y)};
console.log("bb", bb);
return bb;
}
所以你像这样使用它:
var elFrom = document.getElementById("e123");
var elTo = document.getElementById("e456");
var bbox = boundingBoxRelativeToElement(elFrom, elTo);
如果你只想要父元素坐标中的边界框,这将是:
var elFrom = document.getElementById("e123");
var elTo = elFrom.parentElement;
var bbox = boundingBoxRelativeToElement(elFrom, elTo);
即
var elFrom = document.getElementById("e123");
var bbox = boundingBoxRelativeToElement(elFrom, elFrom.parentElement);
或作为辅助函数
function boundingBoxRelativeToParent(el)
{
var bbox = boundingBoxRelativeToElement(el, el.parentElement);
return bbox;
}
然后易于使用:
var bbox = boundingBoxRelativeToParent(document.getElementById("e123"));
(请注意,这假设 parentElement 不为 null(
一种方法是制作一个拉斐尔集并得到它的bb。或者将所有元素包装在 g 中并得到 g 的 bb,我认为 g 应该是最快的。
- 如何在JavaScript中将字符串转换为函数引用
- 如何在Javascript中将JSon对象转换为数组
- 使用JS将数组转换为json对象
- 如何使用json将对象列表从java转换为javascript
- 偶尔结结巴巴地说“;堆叠的”;translate()上的转换(v4.0.0-alpha40)
- 为什么在单独的函数中应用时转换会闪烁/断断续续(D3)
- 在Javascript中转换对象数组
- 将数字转换为一定数量的硬币
- 将纯文本URL转换为可单击链接
- 当图像转换为本地存储的DataURL时,EXIF被删除
- 如何使用js将SNAPSHOT内部版本号转换为3位数的整数
- 如何将TypeScript对象转换为普通对象
- 检测个位数整数时正在转换毫秒
- 从javascript到jquery的转换
- DOM事件通过JSON转换为java
- 将圆柱体转换为弯管
- 混合 ui-sref 和 $state.go 在 Angular ui-router 中进行状态转换
- JavaScript代码问题:我正在将对象转换为数组
- 如何将字母转换为二进制代码
- 获取元素的边界框,说明其转换