diff --git a/apps/web/src/pages/generate/components/FrontendPreviewPlayer.tsx b/apps/web/src/pages/generate/components/FrontendPreviewPlayer.tsx index 6a19c10e2..61ce703f9 100644 --- a/apps/web/src/pages/generate/components/FrontendPreviewPlayer.tsx +++ b/apps/web/src/pages/generate/components/FrontendPreviewPlayer.tsx @@ -141,6 +141,7 @@ const FrontendPreviewPlayer: React.FC = ({ // ── 拖拽状态(用 ref 避免在每帧渲染中触发重渲染)── const draggingTitleRef = useRef(false) + const titleDragRef = useRef(null) const handleTitlePointerDown = useCallback( (e: React.PointerEvent) => { if (!onTitlePositionChange || !playerContainerRef.current) return @@ -152,32 +153,45 @@ const FrontendPreviewPlayer: React.FC = ({ }, [onTitlePositionChange], ) - const handleTitlePointerMove = useCallback( - (e: React.PointerEvent) => { - if (!draggingTitleRef.current || !onTitlePositionChange || !playerContainerRef.current) return - e.preventDefault() - e.stopPropagation() + const handleTitlePointerMove = useCallback((e: React.PointerEvent) => { + if (!draggingTitleRef.current || !playerContainerRef.current) return + e.preventDefault() + e.stopPropagation() + // 拖拽过程中直接修改 DOM,不触发 React 渲染(避免频繁重渲染导致换行) + if (titleDragRef.current) { const rect = playerContainerRef.current.getBoundingClientRect() const relX = Math.max(0, Math.min(rect.width, e.clientX - rect.left)) const relY = Math.max(0, Math.min(rect.height, e.clientY - rect.top)) - const posX = Math.round((relX / rect.width) * playRes.width) - const posY = Math.round((relY / rect.height) * playRes.height) - onTitlePositionChange(posX, posY) + const xpct = (relX / rect.width) * 100 + const ypct = (relY / rect.height) * 100 + titleDragRef.current.style.left = `${xpct}%` + titleDragRef.current.style.top = `${ypct}%` + } + }, []) + const handleTitlePointerUp = useCallback( + (e: React.PointerEvent) => { + if (!draggingTitleRef.current) return + draggingTitleRef.current = false + // 拖拽结束时才调用 onTitlePositionChange 保存最终位置 + if (onTitlePositionChange && playerContainerRef.current) { + const rect = playerContainerRef.current.getBoundingClientRect() + const relX = Math.max(0, Math.min(rect.width, e.clientX - rect.left)) + const relY = Math.max(0, Math.min(rect.height, e.clientY - rect.top)) + const posX = Math.round((relX / rect.width) * playRes.width) + const posY = Math.round((relY / rect.height) * playRes.height) + onTitlePositionChange(posX, posY) + } + ;(e.currentTarget as HTMLDivElement).style.cursor = "grab" + try { + if ((e.currentTarget as Element).hasPointerCapture(e.pointerId)) { + ;(e.currentTarget as Element).releasePointerCapture(e.pointerId) + } + } catch { + /* ignore */ + } }, [onTitlePositionChange, playRes.width, playRes.height], ) - const handleTitlePointerUp = useCallback((e: React.PointerEvent) => { - if (!draggingTitleRef.current) return - draggingTitleRef.current = false - ;(e.currentTarget as HTMLDivElement).style.cursor = "grab" - try { - if ((e.currentTarget as Element).hasPointerCapture(e.pointerId)) { - ;(e.currentTarget as Element).releasePointerCapture(e.pointerId) - } - } catch { - /* ignore */ - } - }, []) const playerContainerRef = useRef(null) const [containerHeight, setContainerHeight] = useState(0) @@ -602,6 +616,7 @@ const FrontendPreviewPlayer: React.FC = ({ padding: "8px 12px", boxShadow: "inset 0 0 0 16px transparent", }} + ref={titleDragRef} onPointerDown={handleTitlePointerDown} onPointerMove={handleTitlePointerMove} onPointerUp={handleTitlePointerUp}