Skip to content

fix(web): initialize SkiaPictureView renderer from a layout effect#3830

Open
svavarstudio wants to merge 1 commit intoShopify:mainfrom
svavarstudio:fix/web-skia-picture-view-renderer-init
Open

fix(web): initialize SkiaPictureView renderer from a layout effect#3830
svavarstudio wants to merge 1 commit intoShopify:mainfrom
svavarstudio:fix/web-skia-picture-view-renderer-init

Conversation

@svavarstudio
Copy link
Copy Markdown

Fixes #3829.

Problem

SkiaPictureView.web.tsx only constructs its WebGLRenderer inside onLayoutEvent, which is wired to the wrapper Platform.View's onLayout (driven by react-native-web's ResizeObserver shim). In several real-world layouts that callback does not fire before the first picture is dispatched, leaving renderer.current === null. The tick rAF loop then drains redrawRequestsRef indefinitely without ever painting, and the canvas stays transparent. Repro and root-cause logging are in #3829.

Fix

Add a useLayoutEffect that initializes the renderer as soon as the <canvas> ref has measurable dimensions. If the canvas hasn't been laid out yet, retry on requestAnimationFrame (bounded to ~600 frames). Guard onLayoutEvent so it doesn't clobber an already-constructed renderer (it remains responsible for the resize path).

This is purely additive — onLayoutEvent still runs first when its event reliably fires, and the new effect is a no-op in that case (early-returns on renderer.current).

Risk / compatibility

  • No public API changes.
  • On platforms where onLayout already fires reliably, the new effect is a no-op.
  • The retry loop is bounded (~10s @ 60fps) and is cancelled on unmount.
  • Native (SkiaPictureView.tsx) is unaffected — only the .web.tsx entry is touched.

Test plan

Manually verified in an Expo + react-native-web app (Chrome 124, Firefox 125, Safari 17) that a <Canvas> inside a flex column with an absolutely-positioned sibling now paints on first commit. Existing web tests in packages/skia/__tests__ still pass locally.

Platform.View's onLayout (driven by react-native-web's ResizeObserver
shim) does not always fire before the first picture is dispatched,
leaving renderer.current === null while the tick loop drains
redrawRequests without ever painting. Initialize the WebGL renderer
from a useLayoutEffect as soon as the <canvas> ref has measurable
layout, with a bounded rAF retry. Guard onLayoutEvent so it does not
clobber an already-constructed renderer.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Web: SkiaPictureView canvas stays blank when wrapper onLayout doesn't fire before the first picture

1 participant