【问题标题】:getBoundingClientRect() returns inaccurate values for complex SVG's in ChromegetBoundingClientRect() 为 Chrome 中的复杂 SVG 返回不准确的值
【发布时间】:2022-01-24 12:16:20
【问题描述】:

我正在尝试计算转换后的 SVG 元素的边界框,为此我正在使用 getBoundingClientRect() 并将 x 和 y 值映射到 SVG 坐标。但是,当形状具有曲线和旋转时,此功能似乎会在 Chrome 和 Edge 中产生错误的输出。另一方面,Firefox 能够产生预期的结果。

这是example。

<svg height="600" width="600">
  <g transform="rotate(-50, 240, 174)" fill="#A1B6FF">        
    <path transform="translate(100, 100)" 
    d="M0, 0 Q 140 128.76 280 0 v 148 Q 140 276.76 0 148 v -148z">
    </path>
  </g>
</svg>

有没有办法像 Firefox 一样更精确地实现这一点?

【问题讨论】:

  • 将组包裹在一个未转换的组中,并使用getBBox()获取包裹器的边界框
  • @enxaneta 我试过了,结果似乎没有改变,至少在 Chrome 中。

标签: javascript google-chrome svg bounding-box getboundingclientrect


【解决方案1】:

我删除了我之前的答案,因为它是完全错误的,希望这是一个更好的答案:

<div>
    <svg id="svg" width="600" height="600" version="1.1" viewBox="0 0 600 600" xmlns="http://www.w3.org/2000/svg">
        <g id="svgElem" transform="rotate(-50, 240, 174)" fill="#A1B6FF">
            <path transform="translate(100, 100)"
                    d="M0, 0 Q 140 128.76 280 0 v 148 Q 140 276.76 0 148 v -148z">
            </path>
        </g>
    </svg>
</div>

<script type="text/javascript">        
    let svgElem = document.getElementById('svgElem');
    
    let bBox = svgElem.getBBox();
    
    console.dir(bBox);

</script>

getBBox 返回的 SVGRect 与 Firefox/Chromium 相同。 但是正如here on MDN

所述

返回值是一个 SVGRect 对象,它定义了边界框。该值与应用于它或父元素的任何转换属性无关

因此,在以这种方式应用转换之前,您总是会得到 svg 的边界框。如果您使用 getBoundingClientRect 来获取 DOMRect,您会发现 Chrome 似乎只是将转换应用于原始边界矩形,然后计算其边界框。

你可以用这样的东西来达到同样的效果(或多或少无用的代码只是为了说明):

<script type="text/javascript">
    const svg = document.getElementById('svg');
    let svgElem = document.getElementById('svgElem');

    const bBox = svgElem.getBBox(); // MDN: The returned value is a SVGRect object, which defines the bounding box. This value is irrespective of any transformation attribute applied to it or the parent elements
    console.dir(bBox);

    const boundingClientRect = svgElem.getBoundingClientRect(); 
    console.dir(boundingClientRect);

    // create a rect without transforms
    const rect1 = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
    rect1.setAttribute('x', bBox.x);
    rect1.setAttribute('y', bBox.y);
    rect1.setAttribute('width', bBox.width);
    rect1.setAttribute('height', bBox.height);
    rect1.setAttribute('fill', '#00ff0040');
    svg.appendChild(rect1);

    const ctm = svgElem.getCTM();

    const topLeftX = ctm.a * bBox.x + ctm.c * bBox.y + ctm.e;
    const topLeftY = ctm.b * bBox.x + ctm.d * bBox.y + ctm.f;

    const topRightX = ctm.a * (bBox.x + bBox.width) + ctm.c * bBox.y + ctm.e;
    const topRightY = ctm.b * (bBox.x + bBox.width) + ctm.d * bBox.y + ctm.f;

    const bottomLeftX = ctm.a * bBox.x + ctm.c * (bBox.y + bBox.height) + ctm.e;
    const bottomLeftY = ctm.b * bBox.x + ctm.d * (bBox.y + bBox.height) + ctm.f;

    const bottomRightX = ctm.a * (bBox.x + bBox.width) + ctm.c * (bBox.y + bBox.height) + ctm.e;
    const bottomRightY = ctm.b * (bBox.x + bBox.width) + ctm.d * (bBox.y + bBox.height) + ctm.f;

    const x = Math.min(topLeftX, topRightX, bottomLeftX, bottomRightX);
    const y = Math.min(topLeftY, topRightY, bottomLeftY, bottomRightY);
    const width = Math.max(topLeftX, topRightX, bottomLeftX, bottomRightX) - x;
    const height = Math.max(topLeftY, topRightY, bottomLeftY, bottomRightY) - y;

    const rect2 = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
    rect2.setAttribute('x', x);
    rect2.setAttribute('y', y);
    rect2.setAttribute('width', width);
    rect2.setAttribute('height', height);
    rect2.setAttribute('fill', '#ff000040');
    svg.appendChild(rect2);

</script>

或者您可以查看 Firefox/Chromium 的开发者工具来查看差异(只是说建立一个小组也不起作用)。

也许 SVG 第 2 版将来会有所作为: Chrome Platfor Status SVG2

那么现在呢?如果 getBBox 是唯一似乎有效但仅适用于没有内部转换的 svg 的函数,是否可以使用 javascript 动态应用这些转换?

原来有人加倍努力: flatten.js

将脚本放在文件“flatten.js”中,如果仍然存在,则删除顶部的剩余部分(html、标题..)

<div>
    <svg id="svg" width="600" height="600" version="1.1" viewBox="0 0 600 600" xmlns="http://www.w3.org/2000/svg">
        <g id="svgElem" transform="rotate(-50, 240, 174)" fill="#A1B6FF">
            <path transform="translate(100, 100)"
                  d="M0, 0 Q 140 128.76 280 0 v 148 Q 140 276.76 0 148 v -148z">
            </path>
        </g>
    </svg>
</div>

<script src="flatten.js"></script>

<script type="text/javascript">
    const svg = document.getElementById('svg');
    let svgElemClone = document.getElementById('svgElem').cloneNode(true); // flatten will directly change the element so a clone is made
    svgElemClone.id = 'svgElemClone';
    svg.appendChild(svgElemClone);

    flatten(svgElemClone, true);

    const bBox = svgElemClone.getBBox();
    console.dir(bBox);        
</script>    

因此,这可能是获得“真实”边界框的一种解决方法。

关于getBoundingClientRect: MDN 说:“返回值是一个 DOMRect 对象,它是包含整个元素的最小矩形,包括它的内边距和边框宽度。”

恕我直言,Chromium 的实现中有一个错误。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-08-11
    • 2015-12-14
    • 1970-01-01
    • 1970-01-01
    • 2018-04-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多