How is the getBBox() SVGRect calculated?

后端 未结 7 1631
再見小時候
再見小時候 2020-12-04 15:29

I have a g element that contains one or more path elements. As I mentioned in another question, I scale and translate the g element by

相关标签:
7条回答
  • 2020-12-04 16:02

    there are at least 2 easy but somewhat hacky ways to do what you ask... if there are nicer (less hacky) ways, i haven't found them yet

    EASY HACKy #1:
    a) set up a rect that matches the "untransformed" bbox that group.getBBox() is returning
    b) apply the group's "unapplied transform" to that rect
    c) rect.getBBox() should now return the bbox you're looking for

    EASY HACKY #2: (only tested in chrome)
    a) use element.getBoundingClientRect(), which returns enough info for you to construct the bbox you're looking for

    0 讨论(0)
  • 2020-12-04 16:05

    The behaviour you see is correct, and consistent with the spec. The transform gets applied, then the bbox is calculated in "current user units", i.e. the current user space. So if you want to see the result of a transform on the element you'd need to look at the bbox of a parent node or similar. It's a bit confusing, but explained a lot better in the SVG Tiny 1.2 spec for SVGLocatable That contains a number of examples that clarify what it's supposed to do.

    0 讨论(0)
  • 2020-12-04 16:07

    I made a helper function, which returns various metrics of svg element (also bbox of transformed element).

    The code is here:

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

    and full functional example is here: http://jsbin.com/acowaq/1

    0 讨论(0)
  • 2020-12-04 16:09

    SVG groups have nasty practice - not to accumulate all transformations made. I have my way to cope with this issue. I'm using my own attributes to store current transformation data which I include in any further transformation. Use XML compatible attributes like alttext, value, name....or just x and y for storing accumulated value as atribute.

    Example:

    <g id="group" x="20" y="100" transform="translate(20, 100)">
    <g id="subgroup" alttext="45" transform="rotate(45)">
    <line...etc...
    

    Therefore when I'm making transformations I'm taking those handmade attribute values, and when writing it back, I'm writing both transform and same value with attributes I made just for keeping all accumulated values.

    Example for rotation:

    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 );
      };
    }
    
    0 讨论(0)
  • 2020-12-04 16:26

    The following code takes into account the transformations (matrix or otherwise) from parents, itself, as well as children. So, it will work on a <g> element for example.

    You will normally want to pass the parent <svg> as the third argument—toElement—as to return the computed bounding box in the coordinate space of the <svg> (which is generally the coordinate space we care about).

    /**
     * @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
      };
    }
    
    0 讨论(0)
  • 2020-12-04 16:29

    People often get confused by the behavioral difference of getBBox and getBoundingClientRect.

    getBBox is a SVG Element's native method as equivalent to find the offset/clientwidth of HTML DOM element. The width and height is never going to change even when the element is rotated. It cannot be used for HTML DOM Elements.

    getBoundingClientRect is common to both HTML and SVG elements. The bounded rectangle width and height will change when the element is rotated or when more elements are grouped.

    0 讨论(0)
提交回复
热议问题