Skip to content
This repository has been archived by the owner on Jun 3, 2020. It is now read-only.

Change initial state of ResizeObservation content size #8

Closed
atotic opened this issue Jun 2, 2016 · 10 comments
Closed

Change initial state of ResizeObservation content size #8

atotic opened this issue Jun 2, 2016 · 10 comments

Comments

@atotic
Copy link
Collaborator

atotic commented Jun 2, 2016

ResizeObserver uses ResizeObservation to store last broadcast content size.
Notification is generated when ResizeObservation.broadcastContentSize != Element.contentSize.

The question is: what should ResizeObservation.broadcastContentSize be initialized to?

Original proposal was to initialize ResizeObservation.broadcastContentSize to Element.contentSize when observation starts. ( ie. when ResizeObserver.observe(Element) is called).

This implies that calling observe() has to trigger layout if dirty, because we need to know Element's size. Triggering layout is undesirable. No more footguns!

Proposal

Initialize ResizeObservation.broadcastContentSize to 0,0. There a lot of of pros:

  • It does not trigger layout reflow on ResizeObserver.observe
  • If the element has layout, it will receive resize notification in the next event loop. Without this notification, developer would have to manually adjust for size when element is created.
  • if the element does not have layout, there will be no notifications.

The only con I could come up with is:

  • every visible registered element will receive a resize notification, even if their size never changes.
@atotic atotic added the Proposal label Jun 2, 2016
@jeremenichelli
Copy link

As I was reading the proposal the last sentence came ot my mind, the initially passed callback will be fired for every element that gets observed. Imagine a developer wants to animate something or do some other scripting, that would get triggered initially probably causing developers to do a hack around the observer to detect if it is that first ghost notification or not.

I understand the concern about the initial layout trigger thing, but to me sounds like the cure is worse than the disease. I don't know if the developer would be able to get around this using contain CSS property that is about to get shipped in Chrome 52 could mitigate that, I prefer forcing the developer to apply a best practice for performance than a hack.

@atotic
Copy link
Collaborator Author

atotic commented Jun 4, 2016

Can you be more specific about your example, I'd like to understand what it does, and why is initial notification problematic? What kind of element would want to

animate something or do some other scripting,

Our canonical examples are components that want to manipulate their children upon resize:

  1. tiled-map fills content area with map tiles
  2. responsive-menu switches between icon and icon+text mode depending on content size.
    Both of these examples benefit from initial resize event.

@jeremenichelli
Copy link

I was thinking more in a case where the developer wants to apply some animation perf technic like Paul Lewis' FLIP, but I realized in that case the initial rects need to be saved by the developer to apply the maths so it wouldn't be a problem there.

Either way, I don't know if I want the callback to be triggered each time I add a target. Can that be an option passed to the observer? Like...

let observer = new ResizeObserver(onchange, { initial: true });

@atotic
Copy link
Collaborator Author

atotic commented Jun 6, 2016

I would not offer an option without a good reason. A good reason is a real-world scenario where option is needed. Options come with implementation cost (code/testing/compatibility), and usage cost: (documentation).

atotic pushed a commit that referenced this issue Jun 9, 2016
@atotic
Copy link
Collaborator Author

atotic commented Jun 9, 2016

Spec has been updated to 0,0 initial value

@devrelm
Copy link

devrelm commented Jun 14, 2016

Speaking as a web dev, I would prefer to not have it notify for the target unless the target size has changed. I think that having observe notify on every call would be a much larger footgun than having observe force a layout update. I am in favor of reverting to checking the target's size at the time of the call to observe.

It is easier and cleaner, if needed for some initialization, to arrange code such that you can call the callback manually like so

let callback = (entries, resizeObserver) => {
    entries.forEach(doWork);
};
let resizeObserver = new ResizeObserver(callback);
resizeObserver.observe(element);
doWork(element, element.getBoundingClientRect());

than it would be to try to ignore the first call to the callback for each targeted element.

Calling window.addEventListener('resize', callback); does not execute callback during the next event loop unless the window's size has actually changed in the meantime. Similarly, I would not expect a ResizeObserver to call my callback unless the size of the element has actually changed.

To address your points:

  • It does not trigger layout on ResizeObserver.observe

I don't see this as having much benefit given that the element's size will be checked and a layout triggered ~1/60 sec later (give or take) and every ~1/60 of a second (give or take) after that during the next browsing context event loop. It is a miniscule performance enhancement with a large development side-effect. Further, as you mentioned, each element ends up being notified during the next even loop which goes further towards negating any gains.

  • If the element has layout, it will receive resize notification in the next event loop. Without this notification, developer would have to manually adjust for size when element is created.

I don't think I quite grasp what you mean by "developer would have to manually adjust for size when element is created." Arranging one's code to do initialization is not difficult, as shown above, and my completely unfounded guess is that no initialization would normally be done. Further "when element is created" makes me think that one would be calling document.createElement(...), at which point they could observe the element before adding it to the DOM, and get notified after it has been added.

  • if the element does not have layout, there will be no notifications.

I don't quite know what you mean by "does not have layout". I assume you mean that the element is not attached to the DOM, in which case I don't think there would be any difference in notifications between initializing to 0,0 or calling getBoundingClientRect(), which would return 0,0. In either case there would be no notifications until the element is added to the DOM (and even then only if the element has size.)

@atotic
Copy link
Collaborator Author

atotic commented Jun 14, 2016

Can you be a bit more specific in your example? An existing UI element, where ResizeObserver would be useful? What exactly does doWork() do?

My reasoning is this:

1. component does not know its initial size
2. component state needs to be in sync with the size
=>
3. component must initially sync its state to the size
=>
4. Getting notification when ResizeObserver observation starts is useful

Which part of this line of reasoning do you disagree with?

It does not trigger layout on ResizeObserver.observe
I don't see this as having much benefit given that the element's size will be
checked and a layout triggered ~1/60 sec later and every ~1/60
of a second (give or take) after that during the next browsing context event loop.

Layout will not be triggered if the initial size does not change.

Similarly, I would not expect a ResizeObserver to call my callback unless the size of the element has actually changed.

I agree that receiving a notification if size has not changed is unintuitive. But I feel that benefit of initial notification is greater, than harm done by unintuitive behavior.

I don't quite know what you mean by "does not have layout".

It is an old term, means that element is not in the display list. Happens when element is not attached, or has display:none.

@devrelm
Copy link

devrelm commented Jun 23, 2016

After some thought over the weekend, I can see your line of thinking. I still don't wholly agree and think that its unintuitiveness will cause confusion, though I admit that I can't think of a case in any work that I've done where an extra call to the callback would cause any huge performance hit or other unwanted calculations. If even only 80% of the time, the initial callback can be run without side-effects, then the remaining 20% of cases are probably at worst split evenly between those cases where the initial callback must be ignored (which could be handled with a fairly simple first-run flag); and those cases where the initial callback is required or at least helpful for initialization purposes, in which case no extra code would need to be written.

Layout will not be triggered if the initial size does not change.

It is my understanding that Chrome and Safari (basically, Webkit & its derivatives) trigger layout calculations on pretty much any size reads (clientHeight, clientWidth, getBoundingClientRect(), et al.)

For full disclosure, I've created the resize-observer package as a polyfill/prototype. It's pretty rudimentary right now and doesn't cover the whole proposal, but I wanted to get it out there. I'll be doing my best to keep it up to date with the proposal.

@atotic
Copy link
Collaborator Author

atotic commented Jun 23, 2016

It is my understanding that Chrome and Safari (basically, Webkit & its derivatives) trigger layout calculations on pretty much any size reads (clientHeight, clientWidth, getBoundingClientRect(), et al.)

All browsers do this, it's the spec. See https://gist.github.com/paulirish/5d52fb081b3570c81e3a

That is why ResizeObserver notifications are passing in contentRect. Hope is that by knowing contentRect you will not have to query properties that trigger layout reflow.

For full disclosure, I've created the resize-observer package as a polyfill/prototype. It's pretty rudimentary right now and doesn't cover the whole proposal, but I wanted to get it out there. I'll be doing my best to keep it up to date with the proposal.

Thanks, we'll need the polyfill.

@waterplea
Copy link

This is pretty terrible if you ask me, to not know if it's an actual resize or just an initial call. Say I want to watch the resize of a DIV with links. Once some links do not fit anymore, I want to hide them under "More" dropdown menu. Since I'm doing stuff that might cause resize in a callback I have to stop observing and start it again after I've done what I wanted. This causes infinite loop because each time I start observing I trigger callback which stops and restarts. This is what we have to regress to due to this decision: #38 (comment)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants