Home > Net >  getBoundingClientRect() returns inaccurate values for complex SVG's in Chrome
getBoundingClientRect() returns inaccurate values for complex SVG's in Chrome

Time:01-03

I'm trying to calculate the bounding box of transformed SVG elements and for that I'm using getBoundingClientRect() and mapping the x and y values to SVG coordinates. However, this function seems to produce wrong outputs in Chrome and Edge when the shape has curves and a rotation. In the other hand, Firefox is able to produce the expected result.

Here's an enter image description here

You would achieve the same with something like this (more or less useless code just for illustration):

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

Or you could just check the Developer tools of Firefox/Chromium to see the dfifferences (just to say putting a group around doesn't work either).

Maybe SVG version 2 will make a difference in the future: enter image description here

enter image description here

So this might be a workaround way to get the "real" bounding box.

As for getBoundingClientRect: MDN says: "The returned value is a DOMRect object which is the smallest rectangle which contains the entire element, including its padding and border-width."

IMHO there is a bug in Chromium's implementation.

  • Related