CSS Zoom property not working with BoundingClientRectangle
Asked Answered
C

5

12

I'm having trouble getting the coordinates of an element after it has been transformed using the "zoom" property. I need to know the coordinates of all 4 corners. I would typically accomplish this with the getBoundingClientRect property however, that does not seem to work correctly when the element is zoomed. I've attached a short jsfiddle link to show what doesn't seem to work. I'm using Chrome but the behavior is present on Firefox as well.

http://jsfiddle.net/GCam11489/0hu7kvqt/

HTML:

<div id="box" style="zoom:100%">Hello</div><div></div><div></div>

JS:

var div = document.getElementsByTagName("div");
div[1].innerHTML = "PreZoom Width: " + div[0].getBoundingClientRect().width;
div[0].style.zoom = "150%";
div[2].innerHTML = "PostZoom Width: " + div[0].getBoundingClientRect().width;

When I run that code, "100" is displayed for both the before and after zoom widths.

I have found a lot of information but everything I find seems to say that it was a known bug on Chrome and has since been fixed. Does anyone know how this can be corrected or what I might be doing wrong?

Conservator answered 31/5, 2017 at 6:40 Comment(1)
Thank you Jesus! I was going nuts trying to figure out why my bounding rectangles were larger than the screen itself. Chromium should be ashamed of themselves for this undocumented behavior.Franklin
O
6

This comment on the bug report goes into some detail reg. the issue, but its status appears to be 'WontFix', so you're probably out of luck there.

Some alternatives:

  • If you were to use transform: scale(1.5) instead of zoom, you'd get the correct value in getBoundingClientRect(), but it would mess with the page layout.
  • You could use window.getComputedStyle(div[0]).zoom to get the zoom value of the element (in decimals) and multiply it with the width from getBoundingClientRect()
Outrun answered 31/5, 2017 at 7:50 Comment(2)
You can not rely on the window.getComputedStyle(div[0]).zoom, because zoom style on parent DomNode is not reflected on current node.Thorvaldsen
Thanks, very useful to know. The Chromium dev who marked WONTFIX is an idiot. I didn't create a "separate coordinate space" on my page. All I did was zoom a few images. For that, I'm punished with bounding rectangles that return invalid nonsense. A pox on Chromium.Franklin
D
4

Had the issue with a tooltip component.

Created this function to fetch all zooms applied to an element. Then I used that zoom to apply it to the global tooltip as well. Once done it aligned correctly.

Notice that it checks for parents parentel.parentElement?.parentElement, this is so it doesn't take the global browser zoom into account.

export const getZoomLevel = (el: HTMLElement) : number[] => {
    const zooms = []
    const getZoom = (el: HTMLElement) => {
        const zoom = window.getComputedStyle(el).getPropertyValue('zoom')
        const rzoom = zoom ? parseFloat(zoom) : 1
        if (rzoom !== 1) zooms.push(rzoom)
        if (el.parentElement?.parentElement) getZoom(el.parentElement)
    }
    getZoom(el)
    zooms.reverse()
    return zooms
}

If you don't want to to zoom the 'tooltip' component would be to get rect with zoom adjusted for, like this:

export const getRect = (el: HTMLElement) : Partial<DOMRect> => {
    if (!el) return { x: 0, y: 0, width: 0, height: 0, top: 0, right: 0, bottom: 0, left: 0 }
    let rect = el?.getBoundingClientRect();
    const zooms = getZoomLevel(el)
    const rectWithZoom = {
        bottom: zooms.reduce((a, b) => a * b, rect.bottom),
        height: zooms.reduce((a, b) => a * b, rect.height),
        left: zooms.reduce((a, b) => a * b, rect.left),
        right: zooms.reduce((a, b) => a * b, rect.right),
        top: zooms.reduce((a, b) => a * b, rect.top),
        width: zooms.reduce((a, b) => a * b, rect.width),
        x: zooms.reduce((a, b) => a * b, rect.x),
        y: zooms.reduce((a, b) => a * b, rect.y),
    }
    return rectWithZoom
}
Deuterogamy answered 6/9, 2022 at 12:56 Comment(1)
Thanks @SindreMA, very useful. The key part is how to retrieve zoom level, which you shared: window.getComputedStyle(el).getPropertyValue('zoom'). Then it's just a simple translation. A thousand blessings upon you.Franklin
S
2

This is quite an old post but I've encountered the same problem, I have an angular material project on a small panel which has a pixel ratio of under 1 which makes everything very small. To fix that I've added a zoom on the body to counter this.

The angular material (7) slider uses getBoundingClientRect() to determine it's position, which made the slider go way further then I'd wish.

I've used a solution like eruditespirit mentioned above.

if (Element.prototype.getBoundingClientRect) {
  const DefaultGetBoundingClientRect = Element.prototype.getBoundingClientRect;
  
  Element.prototype.getBoundingClientRect = function () {
    let zoom = +window.getComputedStyle(document.body).zoom;
    let data = DefaultGetBoundingClientRect.apply(this, arguments);

    if (zoom !== 1) {
      data.x = data.x * zoom;
      data.y = data.y * zoom;
      data.top = data.top * zoom;
      data.left = data.left * zoom;
      data.right = data.right * zoom;
      data.bottom = data.bottom * zoom;
      data.width = data.width * zoom;
      data.height = data.height * zoom;
    }

    return data;
  };
}
Savage answered 9/8, 2019 at 9:3 Comment(1)
Still an issue 5 years later. Why Chromium devs can't be bothered to fix clearly broken behavior because it's "too hard" is beyond comprehension. It's actually very simple. Wherever the element is on the page after zoom, those are its bounding rect coordinates!Franklin
K
1

There has been some activity regarding this in late 2023: https://issues.chromium.org/issues/41461010#comment29

The new behavior adjusts the bounding box to the page's zoom, but not to the CSS zoom. This means that two elements with the same size before zoom, but with different zoom properties, will now have different sized bounding boxes.

But the fix seemed to not work correctly (https://issues.chromium.org/issues/41461010#comment30).

Kelsey answered 12/6 at 15:12 Comment(0)
C
0

I came across this in my search for a solution. I have a position:absolute element that I update the position on when the window is resized, and iOS triggers this on zooming in/out.

I discovered window.visualViewport which has the key information needed to offset this, including zoom information.

My solution for setting the position/size of an element with the zoom offset:

positionNavHighlight(link: HTMLElement) {
  const rect = link.getBoundingClientRect();

  const offsetTop = window.visualViewport.offsetTop;
  const offsetLeft = window.visualViewport.offsetLeft;

  navActiveBg.style.top = `${rect.top + offsetTop}px`;
  navActiveBg.style.left = `${rect.left + offsetLeft}px`;
  navActiveBg.style.height = `${rect.height}px`;
  navActiveBg.style.width = `${rect.width}px`;
}
Cyclic answered 30/6, 2023 at 19:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.