[resize-observer] why we need `ResizeObserver.takeRecords()` or `ResizeObserver.hasRecords()` (the canvas flickering problem)
Nobody has claimed this yet.
- Dominant language
- Bikeshed
- Stars
- 4.9k
- Forks
- 816
- PR merge metrics
- PR metrics pending
Description
Spec: https://drafts.csswg.org/resize-observer/#resize-observer-interface
There's one main problem with ResizeObserver and rendering using canvas:
- people typically use
requestAnimationFramefor animating - ResizeObserver is used for handling size changes
- ResizeObserver callbacks run after animation frame callbacks
- A user's animation frame loop typically includes an update to canvas (f.e. WebGL)
- when ResizeObserver runs after animation frames, a second render-to-canvas will be needed so that the drawing will be correct, or else the problem of canvas flickering will happen due to resize clearing canvas pixels before browser paint. See:
- https://github.com/WICG/resize-observer/issues/37
- https://github.com/pixijs/pixijs/issues/3395
- https://codepen.io/trusktr/pen/EzBKYM (change the variable
SHOW_FLICKER_PROBLEMtotrueat the bottom, then repeatedly resize the example)
- @atotic made some suggestions that are definitely not ideal for users:
-
perform resize inside rAF, then draw
- This is not possible, because ResizeObserver callbacks run after animation frames
- A new animation frame can be requested so that resize happens in the next animation frame before draw, but then the drawing will always be one frame behind, making resize look clunky
-
skip drawing in rAF, draw in RO callback
- This is not possible. The point of RO is to detect size changes, and we don't know about size changes until RO callbacks run. There's no way to know if ResizeObserver has records queued, so that we can conditionally skip drawing in rAF and instead draw in RO callbacks.
-
So, how do we know that we need to avoid drawing in rAF, and instead draw in an RO callback?
That's where this issue comes in.
If we had ResizeObserver.takeRecords(), we could get the records, run the resize logic in rAF, and then draw.
If we had ResizeObserver.hasRecords(), we could skip drawing in rAF, then let the draw happen in the following RO callback.
Note, if we get other observers like ComputedStyleObserver and BoundingClientRectObserver or ClientRectObserver for short, the same problem will exist with those if they run after rAF but before browser paint, and they should also have takeRecords and preferably hasRecords.
An alternative API that would help with these problems would be requestFinalFrame or requestPaintFrame.
The convention for web developers would become:
- animate data with
requestAnimationFrame - handle data updates in observer callbacks without worrying a drawing yet (
ResizeObserver,ComputedStyleObserver,MutationObserver, etc) - finally use
requestFinalFrame(orrequestPaintFrameor whatever name we come up with) to apply final drawing/rendering.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the ResizeObserver interface in the linked CSSWG specification, then read the related ResizeObserver, ComputedStyleObserver, ClientRectObserver, and requestFinalFrame discussions. Reproduce the canvas flicker with the linked CodePen to understand the timing problem. Done means reaching and documenting a standards decision; no repository file or test is named.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- api, web-dev
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100