【问题标题】:How is the getBBox() SVGRect calculated?getBBox() SVGRect 是如何计算的?
【发布时间】:2011-09-04 23:16:30
【问题描述】:

我有一个 g 元素,其中包含一个或多个 path 元素。正如我在another question 中提到的,我通过计算transform 属性来缩放和转换g 元素,使其适合画布另一部分的网格。

计算是使用两个矩形之间的差异完成的,即来自g 元素的getBBox() 和网格周围的矩形。

这是问题——在我进行转换之后,我更新了g 元素的内容并再次调用getBBox()没有删除transform。结果矩形的计算似乎没有考虑transform。我原以为它会反映这种变化。这种行为是否符合 SVG 规范?如何获取变换后的矩形的边界框?

顺便说一句,这是在 Firefox 4 中运行的 HTML 5 文档中,如果这有什么不同的话。

更新:显然这种行为显然违反了规范。来自文字here at w3c

SVGRect getBBox()

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

我读对了吗?如果是这样,这似乎是 Firefox 使用的 SVG 实现中的勘误表;我没有机会尝试任何其他的。如果有人能指出我的位置,我会提交错误报告。

【问题讨论】:

  • 浏览器中的 SVG 支持在 10 年后仍然是成功或失败。 (更不用说微笑了... :( )

标签: svg


【解决方案1】:

显然 getBBox() 没有考虑转换。

我可以在这里指出你,不幸的是我无法让它工作:http://tech.groups.yahoo.com/group/svg-developers/message/22891

【讨论】:

  • 显然这似乎违反了规范——请参阅上面的更新。
  • 它确实考虑了转换,但元素必须放在正在转换的<g>标签内
  • 仅适用于宽度和高度,不提供 x 和 y 坐标。反正不错。谢谢。
  • @fregante:这不是问题。问题是边界框的坐标还考虑了计算边界框的元素上的变换标签,即使该元素不是组。所以你实际上需要取消计算元素本身的变换,如果你想使用元素的父元素内部的坐标,或者在全局空间中,也就是说,你需要在 parentElement 坐标中计算边界框,或者在document.documentElement-坐标。下面看看nikk wong的函数。
  • 这由 mozilla here 记录:"注意: getBBox() 必须在调用该方法时返回实际的边界框——即使在万一该元素尚未被渲染。它也不考虑应用于元素或其父元素的任何转换。”
【解决方案2】:

您看到的行为是正确的,并且符合规范。 应用转换,然后以“当前用户单位”(即当前用户空间)计算 bbox。因此,如果您想查看元素转换的结果,您需要查看父节点或类似节点的 bbox。 这有点令人困惑,但在SVG Tiny 1.2 spec for SVGLocatable 中解释得更好 其中包含许多说明它应该做什么的示例。

【讨论】:

    【解决方案3】:

    至少有 2 种简单但有点老套的方法来做你要求的事情......如果有更好(不那么老套)的方法,我还没有找到它们

    EASY HACKy #1:
    a) 设置一个与 group.getBBox() 返回的“未转换”bbox 匹配的矩形
    b)将组的“未应用变换”应用于该矩形
    c) rect.getBBox() 现在应该返回你正在寻找的 bbox

    EASY HACKY #2:(仅在 chrome 中测试)
    a) 使用 element.getBoundingClientRect(),它会返回足够的信息来构建您正在寻找的 bbox

    【讨论】:

      【解决方案4】:

      SVG 小组有一些糟糕的做法 - 不累积所做的所有转换。我有办法解决这个问题。我正在使用自己的属性来存储当前的转换数据,这些数据会包含在任何进一步的转换中。使用与 XML 兼容的属性,如 alttext、value、name....或仅使用 x 和 y 将累积值存储为属性。

      例子:

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

      因此,当我进行转换时我正在使用那些手工制作的属性值,并且在将其写回时,我正在使用我为保持所有累积而制作的属性编写转换和相同的值价值观。

      旋转示例:

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

      【讨论】:

        【解决方案5】:

        我做了一个辅助函数,它返回 svg 元素的各种指标(也是转换元素的 bbox)。

        代码在这里:

        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

        【讨论】:

          【解决方案6】:

          人们经常对 getBBox 和 getBoundingClientRect 的行为差异感到困惑。

          getBBox 是 SVG 元素的原生方法,相当于查找 HTML DOM 元素的偏移量/客户端宽度。 即使元素旋转,宽度和高度也不会改变。它不能用于 HTML DOM 元素。

          getBoundingClientRect 对 HTML 和 SVG 元素都是通用的。 bounded rectangle 的宽度和高度会在元素旋转或更多元素组合时发生变化。

          【讨论】:

          • 但是如果“translate”变换应用于getBBox,那么为什么不“rotate”变换呢?嘿, traslate 不要改变元素位置。仍然在 x:0 和 y:0 上,尽管您看到它已翻译。
          【解决方案7】:

          以下代码考虑了来自父级、自身以及子级的转换(矩阵或其他)。因此,例如,它将适用于 &lt;g&gt; 元素。

          您通常希望将父 &lt;svg&gt; 作为第三个参数 -toElement 传递给在 &lt;svg&gt; 的坐标空间(通常是我们关心的坐标空间)中返回计算的边界框.

          /**
           * @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
            };
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2011-10-16
            • 2019-02-03
            • 2018-11-03
            • 1970-01-01
            • 1970-01-01
            • 2010-11-27
            • 2013-08-17
            相关资源
            最近更新 更多