[csswg-drafts] [resize-observer] Consider a mode to indicate that a single pass is all that is needed / perform double settlement pass (#6173)

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

== [resize-observer] Consider a mode to indicate that a single pass is all that is needed / perform double settlement pass ==
Spec: https://www.w3.org/TR/resize-observer/#deliver-resize-error

I am sure many of us are familiar with the spec, but the tldr; is that for various reasons, if you change the element you're observing during a `ResizeObserver` event in such a way that it's box changes, then in the next very tick when doing dirty checking it will again be marked for a "change". This deficiency has been known for quite some time, you can see early asks about it here: https://groups.google.com/a/chromium.org/g/blink-dev/c/z6ienONUb5A/m/lDU3VppIAgAJ

However, this is actually desirable at times. For example, in https://github.com/w3c/csswg-drafts/issues/5488#issuecomment-812279513 I outline an example from MDN that does some cool stuff with font-scaling: https://jsfiddle.net/Lxzmh40u/ -- it's the very example that shows off the observer! 

I also have some examples where I have a component that I want to make "more compact" when it gets into a container that is too small. This probably means shrinking some padding, maybe reducing the font-size a tiny bit in some cases, perhaps applying negative margins, which changes the width.

Another concrete example, per the spec:

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

This is perfect for you have a web component hidden behind a tab that is not visible. Then, once it comes into view you can do your JS work to get it looking "right" for the container, this might involve once again adjusting it or making it more "compact" to get the job done. 

You can find an example here that I just wrote: https://jsfiddle.net/cq4oh6f1/19/

The example is contrived since it could be done with a media query as-is since we only have a single thing, but imagine this is a component being placed into some random page and it's very information dense and we want it to be fully responsive. And of course, I could easily fix it by specifying `border-box` as the box-sizing model -- but that is not practical in all cases and there is more than just padding you can change to change an elements size; perhaps sometimes you want to extend the height just a little bit since the width has gotten too small.

Negative margins are another, they can change the width of the box since they're not included in `border-box`. This i s what happens with virtuoso actually; you can find their source here: https://github.com/petyosi/react-virtuoso/blob/1ee6bff1fc2b9d5e61f03271df0f3ff95870882a/src/hooks/useWindowViewportRect.ts

In their case, a single tick of the error is all they get, and there is no infinite loop, which you would expect if the next "change" that gets delivered next frame would cause the change again.

The following solutions are presented on the mailing list and around the web:

```
There is no 1 size fits all. I think the answer is:

- if your component is changing in response to window resize, post an idle task, DOM is pretty messy during window resizes, no one will notice.

- if you are animating the component, be careful with component design. Either design it so its response to resize is fast enough and skip idle task, or post an idle task but draw your component so that bad layout will not be jarring to the user. (

- the two-years from now answer is use CustomPaint instead :-)
```

"There is no 1 size fits all. ", agreed. 

* Putting things into window resize isn't practical all the time -- if you are resizing something coming back from a change in a visibility, you have to know about it. If the user clicks a tab, then the visibility changes and if you just RAF it, you get some jank. ResizeObserver is perfect here -- you can do a sync. update and change styles before paint. However, if you resize (this may be the case for example if the state of things have changed since your component came back into view, for example, it's container has been resized) then some internal bookkeeping may have to be performed. 

* Either design it so its response to resize is fast enough and skip idle task
* Do it in RAF
People can catch 16ms flickers. This is where I've noticed this, not a good solution IMO

You get a jank frame. https://github.com/w3c/csswg-drafts/issues/5488#issuecomment-812279513 has a ton of examples of misunderstanding, but a real life one that is recent here is from Virtuso as well: https://github.com/petyosi/react-virtuoso/issues/269

The video there shows what happens; users will notice. 

How can we fix this? I am not positive but I can propose the following things to get the discussion started:

1. To begin with, let's not trigger every bug logging libraries handler by default when we run into these scenarios: https://github.com/w3c/csswg-drafts/issues/5488. If we solved this, folks would be less irate with the issue overall, since it would be a simple nuissance "warning" instead of worming it's way in. Still, we want a way to capture and find out if we're dropping actual observations vs. ones we don't want to -- thus...

Let's find a way to fix these cases, so that developers can rationalize when they see a warning that "this is something I should look into, some resize events have been dropped and that's bad because jank will happen instead of "better filter these out"

Ideas:

1. Let's define a mechanism to instruct the engine that it's OK to bail after a single dispatch. To `options` when performing an observation, we could add `deliverOnce` that would instruct that handlers running from this will only run once and will not perform layout rechecks. Developers would be responsible for understanding when they turn on this flag on that it means that the layout won't be rechecked after their callbacks run. For many of the above cases, this is desirable and perfectly fine. You would turn this on because you know it's a safe optimization to make. 

Counter arguments:

* _But then users may miss some updates and not realize what's going on._ This can be solved with good documenation; this should not be a default. A user should understand that when they turn this on, the subsequent run should not change the state again or cause other cascading changes on the same element, and if it does, you're on your own. It will be delivered next frame; **no worse than what people are doing today with RAF or simply ignoring the error**.


1. We could also change the bail mechanism to be O(2N) instead of O(N) with the following algorithim adjustment to the specification (3.4.1. Gather active observations at depth):

> - If targetDepth is greater than depth then add observation to activeTargets.
> + If targetDepth is greater than depth OR (targetDepth is equal to depth AND this observation.target has not been visited at "depth" already)  then add observation to activeTargets.

The language could use with some wordsmithing but the outcome is the same:

1. User makes change such as one the ones above.
2. User has the same node checked again once more, and if it 'settles', in that no other change is detected, then it it won't trigger another notifcation and business will go on like usual

Bad stuff:

* _The runtime complexity is now 2N_: Yeah, but if you don't _need_ this behaviour nothing will change. In addition, something that isn't going to spam updates constantly is likely going to read the width / height / box dimensions and immediately realize that nothing else has to be done and so it won't do expensive DOM write / read operations. And of course, many things won't even be a bother. 

Perhaps even both could be done in tandem; there are other proposals that could be done to get more value from this, though, but I think that sums up my feelings.

Happy to hear thoughts. 









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


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

Received on Friday, 2 April 2021 03:10:05 UTC