获取元素的边界框来计算其变换

时间:2012-05-16 17:42:49

标签: javascript svg

鉴于此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. this demo 所示,它不一定是最小边界框(注意上面的圆圈),因为旋转的轴对齐边界框的轴对齐边界框总是增加大小
  3. // 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;
    }
    

5 个答案:

答案 0 :(得分:3)

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

object.getBoundingClientRect();

支持:

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

在这里查看有关它的更多信息。 Link

答案 1 :(得分:2)

一种方法是制作一套raphael并获得它的bb。或者将所有元素包装在g中并获得gb的bb,我假设g应该是最快的。

答案 2 :(得分:2)

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

SVGRect getBBox()     在所有包含的图形元素的几何图形上返回当前用户空间中的紧密边界框(即在应用'transform'属性之后,如果有的话),不包括描边,剪切,蒙版和滤镜效果)。请注意,getBBox必须在调用方法时返回实际的边界框,即使元素尚未呈现。

我想这就是你如何定义'当前用户空间'

答案 3 :(得分: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;
    }
}

答案 4 :(得分:0)

创新。

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

例如您在一个路径中

<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边界框的坐标,创建两个点(左上,右下),将它们转换为目标空间,然后从这两点计算出新的边界框(< strong>请注意,当您仅将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-coordinates中使用boundingBox,则为:

var elFrom = document.getElementById("e123");
var elTo = elFrom.parentElement;
var bbox = boundingBoxRelativeToElement(elFrom, elTo);

aka

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