Problem
It is essentially impossible to observe an element bounding client rect (el.getBoundingClientRect()
) without resorting to a polling solution (for example an animation frame loop).
Trying to implement it without polling is futile. For example see the question and answers here:
It might be possible to ponyfill this hypothetical ClientRectObserver
API without polling, but it will be very convoluted:
- Use InterSection observer (only with ancestors of the target element) and scroll events (on all scrollable ancestors of the target element) to detect possible position changes of the target element’s bounding rect.
- Use MutationObserver to observe every single possible DOM mutation that could ever change the target element’s bounding client rect
- style changes to any ancestors, and siblings within layouts like flexbox, etc
- stylesheet changes to all style and link elements
- forget about CSSOM.
- position:fixed tricks that break with containing block changes
- etc
You get the picture: practically impossible, or at best DOOM ultra-nightmare code.
Solution
A new ClientRectObserver
API would allow us to simply observe an element’s bounding client DOMRect
. It would be something like the following:
const element = document.querySelector('.someElement')
const clientRectObserver = new ClientRectObserver((entries) => {
// any time the client rect could ever change, log it.
for (const entry of entries) {
console.log(entry.target.getBoundingClientRect())
// similar to ResizeObserverEntry/IntersectionObserverEntry:
console.log(entry.contentRect)
console.log(entry.boundingClientRect)
}
})
and done!
Why
There are plenty of reasons to depend on client rects:
- implement new layouts, f.e. anchoring one element on the edge of another
- re-using DOM layout (in hidden elements) to apply to canvas renderings
- rendering things on a WebGL canvas based on DOM element positioning, for example rendering multiple “canvases” using a single overlaid canvas (note, there is a limit of 32 webgl contexts in Chrome as of this writing, so this is a requirement when we need to render multiple little scenes)
- etc
Having this API would not only make things easier for web devs, but help them avoid bugs. I’ve experience bugs with polled bounding rect values and code running before the polled value is up to date. Ideally any bounding rect value would be updated exactly when it should be, and all other code would always have the latest value without worrying about code ordering.
Calling getBoundingClientRect
every single time it is needed in an app solves the issue, but introduces a heavy performance hit because browsers (f.e. Chrome) currently re-calculate bounds on every call.