fix(play): stabilize canvas frame during image swaps

Signed-off-by: baizhi958216 <1475289190@qq.com>
This commit is contained in:
baizhi958216
2026-06-12 22:02:32 +08:00
parent e34306997a
commit ebe39efcac
+20 -15
View File
@@ -349,10 +349,20 @@ export function PlayCanvas({
// the 9:16 image matches the exact device/window — no letterbox. Landscape // the 9:16 image matches the exact device/window — no letterbox. Landscape
// keeps the prior contain-style sizing so the full 16:9 frame stays visible. // keeps the prior contain-style sizing so the full 16:9 frame stays visible.
const sizeStyle: React.CSSProperties = portrait const sizeStyle: React.CSSProperties = portrait
? { width: "100vw", height: "100dvh", objectFit: "cover" } ? { width: "100%", height: "100%", objectFit: "cover" }
: fullViewport : fullViewport
? { maxWidth: "100vw", maxHeight: "100dvh" } ? { width: "100%", height: "100%", objectFit: "contain" }
: { maxWidth: "96vw", maxHeight: "calc(100dvh - 200px)" }; : { width: "100%", height: "100%" };
const canvasStyle: React.CSSProperties = portrait
? { width: "100vw", height: "100dvh" }
: {
width: fullViewport
? "min(100vw, calc(100dvh * 16 / 9))"
: "min(96vw, calc((100dvh - 200px) * 16 / 9))",
aspectRatio: "16 / 9",
maxHeight: fullViewport ? "100dvh" : "calc(100dvh - 200px)",
};
const placeholderStyle: React.CSSProperties = portrait const placeholderStyle: React.CSSProperties = portrait
? { width: "100vw", height: "100dvh" } ? { width: "100vw", height: "100dvh" }
@@ -382,19 +392,14 @@ export function PlayCanvas({
{imageUrl ? ( {imageUrl ? (
<div <div
className="relative inline-block" className="relative"
style={{ boxShadow: fullViewport ? "none" : SHADOW }} style={{ ...canvasStyle, boxShadow: fullViewport ? "none" : SHADOW }}
> >
{/* Background image — Runware CDN URL or data URI (mock mode). {/* The stable wrapper owns the frame size. Keeping overlay geometry
The width/height attributes give the browser the intrinsic aspect independent of <img> decode/source swaps prevents controls from
ratio (1792:1024 landscape / 1024:1792 portrait) so that, while the jumping when a newly generated image is committed. */}
bytes are still arriving from the CDN, the <img> reserves the right
box instead of collapsing to a one-pixel sliver — fixes the
"等很久 → 一根线 → 突然出图" jank. Landscape uses w-auto/h-auto +
maxWidth/maxHeight (contain); portrait switches sizeStyle to
100vw×100dvh with object-fit:cover (full-bleed, no letterbox). */}
<img <img
key={imageUrl.slice(-48)} key={imageUrl}
ref={imgRef} ref={imgRef}
src={imageUrl} src={imageUrl}
width={intrinsicW} width={intrinsicW}
@@ -402,7 +407,7 @@ export function PlayCanvas({
alt="Generated scene" alt="Generated scene"
onClick={handleImageClick} onClick={handleImageClick}
draggable={false} draggable={false}
className={`block ${portrait ? "" : "w-auto h-auto"} select-none animate-fade-in transition-opacity duration-700 ease-out ${ className={`block select-none animate-fade-in transition-opacity duration-700 ease-out ${
imageClickable ? "cursor-pointer" : interactive ? "cursor-default" : "cursor-wait" imageClickable ? "cursor-pointer" : interactive ? "cursor-default" : "cursor-wait"
} ${dimmed ? "opacity-40" : "opacity-100"}`} } ${dimmed ? "opacity-40" : "opacity-100"}`}
style={sizeStyle} style={sizeStyle}