Skip to content

[resize-observer] Browsers trigger on initial observe of disconnected element, contrary to spec #11280

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
joliss opened this issue Nov 27, 2024 · 1 comment
Labels
Closed as Question Answered Used when the issue is more of a question than a problem, and it's been answered. resize-observer-1

Comments

@joliss
Copy link

joliss commented Nov 27, 2024

There seems to be disagreement between the resize-observer spec and browser implementations as to whether observing a disconnected element should trigger an immediate callback, as in the following code:

let disconnectedEl = document.createElement("div");
let observer = new ResizeObserver((entries) => {
  console.log("Got callback:", entries);
});
observer.observe(disconnectedEl);

Behavior according to resize-observer spec

The resize-observer chapter § 1. Introduction lists the following among the "interesting facts":

Observation will fire when observation starts if Element is being rendered, and Element’s size is not 0,0.

The rest of the spec does seem to agree with this conclusion: Section § 3.1. ResizeObservation example struct says that lastReportedSizes is initialized to [(0,0)], and the isActive() function compares the element's currentSize (which is its computedSize) to that. But the computedSize of a disconnected element should be 0,0, and thus isActive() should be false.

So my reading is that the observer should not immediately fire, because the element isn't being rendered yet.

Current browser behavior

Running the code above, Chrome, Firefox and Safari all do immediately fire, printing

Got callback: Array [ ResizeObserverEntry ]

where the box sizes in the ResizeObserverEntry array are indeed all 0,0.

Suggested resolution

I'm not sure whether the browser behavior should be changed, or the spec should be brought in line with browsers' actual behavior.

joliss added a commit to joliss/lit that referenced this issue Nov 28, 2024
…it#4831

The `ResizeController` would previously manually trigger an initial callback
(which would get skipped if `skipInitial` is true), but this isn't necessary, as
the browser's `ResizeObserver` already triggers the callback when observing
initially[1]. So this commit rewrites the logic to instead suppress the initial
time the `ResizeObserver` fires for a given element if `skipInitial` is true.

Further improvements:

- When `controller.observe(el)` is called while disconnected, we don't
  immediately call `observer.observe(el)` , as this would cause a duplicate
  `.observe` call when reconnecting.
- The test suite would previously pass despite `skipInitial` not working
  properly. We add `await resizeComplete()` in a few places to wait two
  animation frames, so that we now test the behavior properly.

I considered whether `skipInitial` should just be removed altogether, but I
think it's actually useful for Lit specifically: Without `skipInitial`, you'll
often get an extraneous `updated()` lifecycle callback after the initial
component render, as the `ResizeObserver` only triggers *after* the component
has already been rendered. This behavior isn't very useful, and so `skipInitial`
can help avoid the performance impact of this.

[1] Minor aside: According to the spec, this initial callback should only happen
if the observed element is connected to the DOM. However, current browser
implementations seem to trigger the callback
[regardless](w3c/csswg-drafts#11280). This distinction
likely isn't important for `ResizeController`'s production code, as we only
observe while the host component is connected. However, I took care to avoid
relying on this behavior in the test suite, in case browsers change it to
conform to the spec down the line.
joliss added a commit to joliss/lit that referenced this issue Nov 28, 2024
…it#4831

The `ResizeController` would previously manually trigger an initial callback
(which would get skipped if `skipInitial` is true), but this isn't necessary, as
the browser's `ResizeObserver` already triggers the callback when observing
initially[1]. So this commit rewrites the logic to instead suppress the initial
time the `ResizeObserver` fires for a given element if `skipInitial` is true.

Further improvements:

- When `controller.observe(el)` is called while disconnected, we don't
  immediately call `observer.observe(el)` , as this would cause a duplicate
  `.observe` call when reconnecting.
- The test suite would previously pass despite `skipInitial` not working
  properly. We add `await resizeComplete()` in a few places to wait two
  animation frames, so that we now test the behavior properly.

I considered whether `skipInitial` should just be removed altogether, but I
think it's actually useful for Lit specifically: Without `skipInitial`, you'll
often get an extraneous `updated()` lifecycle callback after the initial
component render, as the `ResizeObserver` only triggers *after* the component
has already been rendered. This behavior isn't very useful, and so `skipInitial`
can help avoid the performance impact of this.

[1] Minor aside: According to the spec, this initial callback should only happen
if the observed element is connected to the DOM. However, current browser
implementations seem to trigger the callback
[regardless](w3c/csswg-drafts#11280). This distinction
likely isn't important for `ResizeController`'s production code, as we only
observe while the host component is connected. However, I took care to avoid
relying on this behavior in the test suite, in case browsers change it to
conform to the spec down the line.
@Loirooriol
Copy link
Contributor

See resolution in #3664 (comment)

#7808 is discussing some details

@Loirooriol Loirooriol added the Closed as Question Answered Used when the issue is more of a question than a problem, and it's been answered. label Jan 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Closed as Question Answered Used when the issue is more of a question than a problem, and it's been answered. resize-observer-1
Projects
None yet
Development

No branches or pull requests

2 participants