34

鉴于此 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/

计算元素相对于其父元素的边界框的最简单、最有效的方法是什么?


以下是我迄今为止提出的最佳答案,但有两个问题:

  1. 似乎需要做很多工作才能更好地处理,并且
  2. 正如在这个演示中看到的,它不一定是最小边界框(注意上面的圆圈),因为旋转的轴对齐边界框的轴对齐边界框总是会增加大小。

 

// 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;
}
4

6 回答 6

4

您只需要使用这个简单的 Javascript 函数:

object.getBoundingClientRect();

它支持:

Chrome  Firefox (Gecko)   Internet Explorer Opera   Safari
1.0           3.0 (1.9)   4.0               (Yes)   4.0

在这里查看更多关于它的信息。关联

于 2013-12-31T13:09:42.497 回答
2

这似乎是当前方法,除非/直到 getBBox() 将对象上的变换视为直接定义边界框,即使SVG 1.1 SE 定义

SVGRect getBBox() 返回当前用户空间中所有包含的图形元素几何上的紧密边界框(即,在应用“变换”属性后,如果有的话),不包括描边、剪切、遮罩和过滤效果)。请注意,getBBox 必须在调用该方法时返回实际的边界框,即使元素尚未被渲染也是如此。

我想这都是关于你如何定义“当前用户空间”的

于 2014-10-02T19:51:48.817 回答
2

一种方法是制作一个拉斐尔集并获得它的bb。或者将所有元素包装在 g 中并获得 g 的 bb,我认为 g 应该是最快的。

于 2012-11-27T20:15:10.307 回答
2

这是一个解释旋转椭圆的答案。转换方法适用于矩形,但椭圆返回一个比对象旋转时大得多的边界框。

使用内置 JavaScript 函数的更简单的答案对我来说不是很有用,因为它仍然必须被转换回绘图空间。

此代码不处理椭圆的倾斜或缩放(但对于任何其他对象),但在我的程序中http://nwlink.com/~geoffrey/svgBuild我打算通过绘制一个全新的椭圆来处理倾斜和缩放应用了倾斜或比例。

椭圆的公式来自这里:How do you calculate the axis-aligned bounding box of an ellipse?非常感谢那里的帮助。

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;
    }
}
于 2016-12-03T17:18:09.583 回答
1

如果您不想处理数学问题,一个好的解决方法是将 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()
    }

于 2021-12-12T01:05:07.927 回答
1

死灵术。

您需要的边界框坐标始终与您要在其中使用坐标的元素的变换相关。

例如,您在 a 中有一条路径

<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);

如果你只想要 parentElement 坐标中的 boundingBox,这将是:

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 不为空)

于 2020-11-19T10:31:42.093 回答