fix(play): gate scene transition on image decode

Keep the "transitioning" overlay visible until the <img> element's
bitmap is fully decoded, so the user never sees progressive paint
or a blank flash between scenes.

- Add onImageReady callback to PlayCanvas (<img onLoad> + decode())
- Delay setPhase("ready") until decode resolves (3s timeout fallback)
- Applied to all 4 scene entry paths: prebaked card, live /api/start,
  performSceneTransition, and recorded replay transition

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
yuanzonghao
2026-06-13 11:43:35 +08:00
parent c4ffc16498
commit e3ee3547e5
2 changed files with 45 additions and 3 deletions
+8
View File
@@ -183,6 +183,7 @@ export function PlayCanvas({
playerName,
visionClickEnabled = true,
onOpenSettings,
onImageReady,
aboveCanvas,
aboveCanvasLeft,
belowCanvas,
@@ -207,6 +208,7 @@ export function PlayCanvas({
// 选择节点点击背景是否触发识图。关闭时背景点击保持静默,用户只能点选项。
visionClickEnabled?: boolean;
onOpenSettings?: () => void;
onImageReady?: () => void;
// 渲染在图片正上方、右对齐的 slot(画面外、紧贴右上角)。
aboveCanvas?: ReactNode;
// 渲染在图片正上方、左对齐的 slot(画面外、紧贴左上角),与 aboveCanvas 水平镜像。
@@ -407,6 +409,12 @@ export function PlayCanvas({
alt="Generated scene"
onClick={handleImageClick}
draggable={false}
onLoad={() => {
if (!onImageReady) return;
const el = imgRef.current;
if (!el) { onImageReady(); return; }
el.decode().then(onImageReady, onImageReady);
}}
className={`block select-none animate-fade-in transition-opacity duration-700 ease-out ${
imageClickable ? "cursor-pointer" : interactive ? "cursor-default" : "cursor-wait"
} ${dimmed ? "opacity-40" : "opacity-100"}`}