diff --git a/components/canvas/nodes/ImageNode.tsx b/components/canvas/nodes/ImageNode.tsx index 82abc3303..c324b0b35 100644 --- a/components/canvas/nodes/ImageNode.tsx +++ b/components/canvas/nodes/ImageNode.tsx @@ -2057,8 +2057,12 @@ export function ImageNode({ // 文件输入的 ref const fileInputRef = useRef(null); const containerRef = useRef(null); - const [localEditPromptLayerElement, setLocalEditPromptLayerElement] = - useState(null); + const [localEditPromptPortalFrame, setLocalEditPromptPortalFrame] = useState<{ + left: number; + top: number; + width: number; + height: number; + } | null>(null); const displayImageRef = useRef(null); const compareFrameRef = useRef(null); const cropOverlayRef = useRef(null); @@ -2493,6 +2497,59 @@ export function ImageNode({ })), [localEditPromptInlineOverlays, nodeContentScale] ); + useEffect(() => { + if ( + !localEditMode || + !data.src || + localEditPromptViewportOverlays.length === 0 || + typeof window === "undefined" + ) { + setLocalEditPromptPortalFrame(null); + return; + } + + let animationFrame = 0; + const updatePortalFrame = () => { + const rect = containerRef.current?.getBoundingClientRect(); + if (!rect) { + return; + } + + const nextFrame = { + left: rect.left, + top: rect.top, + width: rect.width, + height: rect.height, + }; + setLocalEditPromptPortalFrame((currentFrame) => { + if ( + currentFrame && + Math.abs(currentFrame.left - nextFrame.left) < 0.5 && + Math.abs(currentFrame.top - nextFrame.top) < 0.5 && + Math.abs(currentFrame.width - nextFrame.width) < 0.5 && + Math.abs(currentFrame.height - nextFrame.height) < 0.5 + ) { + return currentFrame; + } + return nextFrame; + }); + }; + const schedulePortalFrameUpdate = () => { + window.cancelAnimationFrame(animationFrame); + animationFrame = window.requestAnimationFrame(updatePortalFrame); + }; + + updatePortalFrame(); + const intervalId = window.setInterval(updatePortalFrame, 250); + window.addEventListener("resize", schedulePortalFrameUpdate); + window.addEventListener("scroll", schedulePortalFrameUpdate, true); + return () => { + window.cancelAnimationFrame(animationFrame); + window.clearInterval(intervalId); + window.removeEventListener("resize", schedulePortalFrameUpdate); + window.removeEventListener("scroll", schedulePortalFrameUpdate, true); + }; + }, [data.src, localEditMode, localEditPromptViewportOverlays.length]); const currentPickerClickStyle = useMemo(() => { if (!boxCutoutMetrics || !currentClick) return null; @@ -8147,10 +8204,20 @@ export function ImageNode({ ); })} - {localEditPromptLayerElement + {typeof document !== "undefined" && localEditPromptPortalFrame ? createPortal( - localEditPromptViewportOverlays.map( - ({ region, isEditing, style }) => ( +
+ {localEditPromptViewportOverlays.map( + ({ region, isEditing, style }) => (
) : null}
- ) - ), - localEditPromptLayerElement + ) + )} +
, + document.body ) : null} {localEditDraftRectStyle ? ( @@ -8829,10 +8897,6 @@ export function ImageNode({ ) : null} -