[csswg-drafts] Add API for getting the visible bounding rect an element can be placed inside (#6132)

keithamus has just created a new issue for https://github.com/w3c/csswg-drafts:

== Add API for getting the visible bounding rect an element can be placed inside ==
Originally from https://github.com/whatwg/dom/issues/964

I guess this is related to multiple parts of the spec, [css-overflow-4](https://github.com/w3c/csswg-drafts/tree/main/css-overflow-4), [css-position-3](https://github.com/w3c/csswg-drafts/tree/main/css-position-3)

When designing a system for [popovers](https://en.wikipedia.org/wiki/Popover_(GUI)), we came across a rather tricky area of the DOM: getting a computed rectangle of the range of visible pixels an element could be placed in, such that the top-left pixel is visible. I think the specs refer to this concept as a `scrollport`.

To achieve this is difficult because most DOM APIs to get these dimensions are either _speculative_, in that they will provide a "suggested" value, e.g. `.style.height`, or _actualized_, for example `getBoundingClientRect()` (which gets real pixels on the screen but doesn't account for overflow). The answer to the question "where can an element render and still be visible in the viewport" is somewhere in between, as it includes accidental complexity the CSS box model (borders are clipping, overflow only impacts positioned elements), API idiosyncrasies (`document` is not an element and so has no `getBoundingClientRect()`, `document.body` nor `document.body.parentElement` are `overflow: visible` by default, despite the window having a scrollbar).

The answer comes after much edge-case testing, the behaviour is something like:

 - Traverse up the tree until a "clipping" element is found
   - A clipping element is defined as an element which is `overflow: scroll`, and _not_ `position: static`.
 - If a clipping element is not found, and we've traversed up to the body
    - document.body has specific edge cases. It is not a clipping element, but the `window` is, so enact specific behavior here.
 - Take the clipping elements bounding rect, and the border, to get the "non clipping area" of the containing element.
 
```js
const element = document.querySelector('the-element-to-position')
// The top level clipping rect is the window. This is the largest possible container, if all elements between 
const clippingRect = {top: 0, left: 0, right: window.innerWidth, bottom: window.innerHeight}
{
  let parent = element
  while (parent !== null && parent !== document.body) {
    const {overflow, position, borderLeftWidth, borderRightWidth, borderTopWidth, borderBottomWidth} = getComputedStyle(parent)
    const borderLeft = parseInt(borderLeftWidth, 10)
    const borderRight = parseInt(borderRightWidth, 10)
    const borderTop = parseInt(borderTopWidth, 10)
    const borderBottom = parseInt(borderBottomWidth, 10)
    if (
      overflow !== 'visible' && // Overflow needs to be checked as this is the rect which will clip rendering
      position !== 'static' // If a position is "static" then it does not "contain" elements, so `overflow` styles have no impact
    ) {
      const {top, left, width, height} = parent.getBoundingClientRect()
      clippingRect.top = top + borderTop // borders need to be accounted for as they clip
      clippingRect.left = left + borderLeft  // borders need to be accounted for as they clip
      clippingRect.right = width - borderLeft - borderRight // borders need to be accounted for as they clip
      clippingRect.bottom = height - borderTop - borderBottom // borders need to be accounted for as they clip
    }
    parent = parent.parentElement
  }
}
```

This will get an absolute clipping rect, which gives the dimensions that an element is able to render into. It does not account for any positioned elements and how that may affect positioning.

There exists a ton of code in the wild for popups and their variants (toasts, tooltips, menus, dialogs) and all _require_ this kind of code. Some fall back for detecting if the container sits outside the windows dimensions but this can cause all kinds of overflow bugs as soon as they're nested inside an overflow container.

This feels like something the browser would keep track of during a rendering path? Is it possible to expose this kind of "clipping rect" via a DOM API?

Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/6132 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Wednesday, 24 March 2021 14:46:46 UTC