fix(play): lock orientation pre-paint to avoid portrait loading flash
Set the session orientation in an isomorphic layout effect so portrait phones don't flash the landscape loading chrome for a frame before the bootstrap effect runs. State still inits to "landscape" for SSR-safety; the correction now lands before first paint (no-op on landscape devices). Addresses Copilot review on PR #31. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+20
-1
@@ -6,6 +6,7 @@ import {
|
|||||||
Suspense,
|
Suspense,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
useLayoutEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
@@ -70,6 +71,13 @@ function detectOrientation(): Orientation {
|
|||||||
return portrait && coarse ? "portrait" : "landscape";
|
return portrait && coarse ? "portrait" : "landscape";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Runs before the browser paints (so it can correct first-frame state without a
|
||||||
|
// visible flash), but useLayoutEffect warns when called during SSR. PlayInner
|
||||||
|
// only ever renders on the client (/play prerenders the Suspense fallback), yet
|
||||||
|
// fall back to useEffect on the server anyway to keep the warning out.
|
||||||
|
const useIsomorphicLayoutEffect =
|
||||||
|
typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
||||||
|
|
||||||
// Cap how long we wait for the browser to download + decode a scene image
|
// Cap how long we wait for the browser to download + decode a scene image
|
||||||
// before giving up and rendering anyway. Runware's CDN is usually <2s for a
|
// before giving up and rendering anyway. Runware's CDN is usually <2s for a
|
||||||
// 1792×1024 PNG, but over slow links / VPN / strict corp networks the same
|
// 1792×1024 PNG, but over slow links / VPN / strict corp networks the same
|
||||||
@@ -797,6 +805,16 @@ function PlayInner() {
|
|||||||
};
|
};
|
||||||
}, [togglePresentation, presentation]);
|
}, [togglePresentation, presentation]);
|
||||||
|
|
||||||
|
// Lock the visible orientation BEFORE the first paint, so portrait phones
|
||||||
|
// never flash the landscape loading chrome. The state inits to "landscape"
|
||||||
|
// for SSR-safety; this corrects it pre-paint (no-op re-render on landscape
|
||||||
|
// devices). Prebaked cards (decision C) stay landscape-baked regardless of
|
||||||
|
// device. The bootstrap effect below re-derives the same value for the
|
||||||
|
// /api/start payload.
|
||||||
|
useIsomorphicLayoutEffect(() => {
|
||||||
|
setOrientation(params.get("card") ? "landscape" : detectOrientation());
|
||||||
|
}, [params]);
|
||||||
|
|
||||||
// ── Bootstrap: start session ─────────────────────────────────────────
|
// ── Bootstrap: start session ─────────────────────────────────────────
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (startedRef.current) return;
|
if (startedRef.current) return;
|
||||||
@@ -848,10 +866,11 @@ function PlayInner() {
|
|||||||
// Lock orientation for the whole session. Prebaked cards (decision C) are
|
// Lock orientation for the whole session. Prebaked cards (decision C) are
|
||||||
// landscape-baked, so they stay landscape regardless of device; only the
|
// landscape-baked, so they stay landscape regardless of device; only the
|
||||||
// live /api/start path requests a portrait paint when the phone is upright.
|
// live /api/start path requests a portrait paint when the phone is upright.
|
||||||
|
// The visible state is already set pre-paint by the layout effect above;
|
||||||
|
// here we only need the value for the /api/start payload.
|
||||||
const sessionOrientation: Orientation = cardName
|
const sessionOrientation: Orientation = cardName
|
||||||
? "landscape"
|
? "landscape"
|
||||||
: detectOrientation();
|
: detectOrientation();
|
||||||
setOrientation(sessionOrientation);
|
|
||||||
if (livePayload) livePayload.orientation = sessionOrientation;
|
if (livePayload) livePayload.orientation = sessionOrientation;
|
||||||
|
|
||||||
if (!cardName && !livePayload) {
|
if (!cardName && !livePayload) {
|
||||||
|
|||||||
Reference in New Issue
Block a user