whatwg / dom

DOM Standard
https://dom.spec.whatwg.org/
Other
1.58k stars 295 forks source link

Proposal: add `computed` attribute for great performance! #1318

Open anuoua opened 1 week ago

anuoua commented 1 week ago

What problem are you trying to solve?

When we need to frequently read some values ​​from DOM Element, the browser will perform reflow, which is a very large performance consumption. For example, read scrollTop, getBoundingClientRect. I believe there are many scenarios that need to frequently read the changes of these values, but doing these now will affect performance.

Why not pre-store the value in Element when calculating the next frame inside the browser to avoid reflowing again when reading, so as to greatly improve performance?

If pre-storing the calculated value for each element will consume too many resources, then we can support a property to indicate that the specified element needs to pre-store the calculated value, which will greatly improve performance without wasting resources.

I temporarily think of using computed. If there is a better proposal, please discuss it.

WebReflection commented 1 week ago

I feel like this could be lazy or helped via CSS similar way will-change helps but using will-compute or similar so that hints are obvious and could be optimized on the right side of affairs? 🤔

anuoua commented 1 week ago

I feel like this could be lazy or helped via CSS similar way will-change helps but using will-compute or similar so that hints are obvious and could be optimized on the right side of affairs? 🤔

will-compute seems better, but how to specify the pre-store computed value?

will-compute: width height scrollTop;

It looks weird.

Or use keywords

will-compute: rect scroll;

rect will pre-store getBoundingClientRect, scroll will pre-store scrollTop.

There are many more computed values ​​that need to be listed.

WebReflection commented 1 week ago

mine was just an idea but I like rect and scroll ... my idea is that you don't want those properties computed for every possible DOM element on the page so it's gotta be hinted by lazy accessor, which might be complicated, or CSS properties you would retrieve via getComputedStyle instead, if that makes sense. Anyway, curious to hear from anyone else around this but I think it's a sensible request/requirement to me (imho), I don't know how complicated it would be to have/implement though.

Kaiido commented 1 week ago

Most browsers (all?) already do "store" the full computed box values and only recompute it when the layout has been "dirtied". So doing something like

for (const elem of elems) {
  const rect = elem.getBoundingClientRect();
  // do something with rect
}

is not in itself "bad". What's bad is when in the // do something the layout is dirtied. As long as nothing modifies the DOM or CSSOM in that loop, a single reflow will happen.

So this could actually be done in user-land by running a first loop that will gather all the needed values, and a second loop that will use these gathered values. This obviously is fragile as it requires no other code did dirty the layout before you gathered the values, but while having an out-of-date state accessible might be useful in some (rare?) cases, a lot of APIs and properties do need an up-to-date box-model and thus trigger reflows. I don't think a single property can hold all of these (e.g. how would you call document.getElementFromPoint() from your design?), and some like scrollTo() will anyway trigger a reflow because they both read the computed scroll value synchronously and dirty it right away...

Unfortunately there are so many different things that do trigger a reflow that a catch-all solution seems almost impossible.

270 could offer a nice path toward separating better DOM changes and reading, but that would certainly not solve the whole problem either.

Personally, to cope with this issue I use a strategy which registers a couple of callbacks: one getting the computed values after a ResizeObserver callback (and thus after the browser's own layout recalc), and another that will dirty the box-model after all the other "reading callbacks" have been executed. But for it to work, all the code that does read the layout has to go through this strategy (i.e. most libraries might just break the model), and we have to be sure that nothing will read the layout again in the "dirty" callbacks, which is not easy at all, e.g. did you know that canvas2DContext.fillText() does need a reflow? And then once again some methods like scrollTo() are just evil here.

WebReflection commented 1 week ago

@Kaiido wow!

did you know that canvas2DContext.fillText() does need a reflow?

I had no idea even that static rectangle would trigger a reflow but thanks for the details and everything reasons well to me ... back to CSS wonderland, we have an easy way to write or read properties from JS but no way to be reactive ... would it be possible or make sense to have a way to hook into a CSS variable so that, when it changes, more can be done?

I know this might feel like looking for troubles, same way resize triggering a resize would do, but I am thinking if --reactive-width: computed(--width) where --width uses some calc or other metrics to eventually change, could be a partial solution (or a side-solution) to this issue, so that one could address the "reactivity" by selectors and handle changes around all possible properties whenever these happen?

Kaiido commented 6 days ago

I must admit I'm not entirely sure what exact use case you have in mind, but indeed most might certainly be handled on their own with alternative proposals. For instance I think @container does solve a lot of cases where before one needed to use computed boxes in JS. Your proposed solution makes me think of https://github.com/w3c/csswg-drafts/issues/8982 which would also help solve a bunch of other use cases, though still not the whole problem.