javascript - getBoundingClientRect() 在 Chrome 中为复杂的 SVG 返回不准确的值

标签 javascript google-chrome svg bounding-box getboundingclientrect

我正在尝试计算转换后的 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 那样更精确地实现这一点?

最佳答案

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

<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

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

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

enter image description here

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

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

enter image description here

enter image description here

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

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

恕我直言,Chromium 的实现中存在错误。

关于javascript - getBoundingClientRect() 在 Chrome 中为复杂的 SVG 返回不准确的值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70463171/

相关文章:

Javascript - 按条件使用链式函数的不同部分

javascript - ServiceWorker 数据存储在哪里?

html - <svg> 元素匹配子元素的宽度和高度

css - SVG 不通过 css 在网站中显示

javascript - React Native 从任意数据创建 blob

javascript - Firefox javascript 变得无响应警告

google-chrome - Chrome 和 Firefox 之间的 d3 缩放差异

css - chrome 中不需要的单杠

javascript - SVG 填充中的 event.target?

javascript - 如何使这些数据超出此内联函数的范围?