w3c / w3c/csswg-drafts

[resize-observer] why we need `ResizeObserver.takeRecords()` or `ResizeObserver.hasRecords()` (the canvas flickering problem)

Open
#9,717 3 comments 5 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

resize-observer-1
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 requestAnimationFrame for 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:
  • @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 (or requestPaintFrame or whatever name we come up with) to apply final drawing/rendering.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.