fix(title): 拖拽时用ref直接改DOM避免React重渲染导致换行
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 6m59s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Failing after 14m21s
PR Automation / Auto Approve on CI Green (pull_request) Failing after 14m22s
AI Code Review / AI Code Review (pull_request) Failing after 14m23s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m30s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 3m24s
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 3m13s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 3m39s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 3m44s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 7m33s
CI/CD Pipeline / CI Gate (pull_request) Failing after 6s
CI/CD Pipeline / Production Browser E2E (pull_request) Failing after 360h20m30s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 360h20m34s
CI/CD Pipeline / Canary Release to Production (pull_request) Failing after 360h20m30s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 360h20m32s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 360h27m17s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 360h27m21s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 360h27m23s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Failing after 360h27m27s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Failing after 360h27m29s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 360h27m59s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 360h28m1s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 360h28m9s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 360h28m9s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 360h28m11s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 360h54m51s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 361h1m37s
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Failing after 361h1m43s
CI/CD Pipeline / PR Build API Image (pull_request) Failing after 361h1m56s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 361h2m26s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 360h54m50s
CI/CD Pipeline / PR Build Worker Image (pull_request) Failing after 361h1m49s

handleTitlePointerMove 每次 mousemove 都调用 onTitlePositionChange 触发
React 频繁重渲染,wordBreak:break-word 导致标题文字换行。

修复:拖拽过程中用 titleDragRef 直接修改 DOM style.left/top,
不触发 React 渲染;只在 handleTitlePointerUp(拖拽结束)时调用
onTitlePositionChange 保存最终位置。
This commit is contained in:
xiaoxia
2026-09-01 14:32:19 +08:00
parent adf6b517fb
commit 89413d6822
@@ -141,6 +141,7 @@ const FrontendPreviewPlayer: React.FC<FrontendPreviewPlayerProps> = ({
// ── 拖拽状态(用 ref 避免在每帧渲染中触发重渲染)──
const draggingTitleRef = useRef(false)
const titleDragRef = useRef<HTMLDivElement>(null)
const handleTitlePointerDown = useCallback(
(e: React.PointerEvent<HTMLDivElement>) => {
if (!onTitlePositionChange || !playerContainerRef.current) return
@@ -152,32 +153,45 @@ const FrontendPreviewPlayer: React.FC<FrontendPreviewPlayerProps> = ({
},
[onTitlePositionChange],
)
const handleTitlePointerMove = useCallback(
(e: React.PointerEvent<HTMLDivElement>) => {
if (!draggingTitleRef.current || !onTitlePositionChange || !playerContainerRef.current) return
e.preventDefault()
e.stopPropagation()
const handleTitlePointerMove = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
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<HTMLDivElement>) => {
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<HTMLDivElement>) => {
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<HTMLDivElement>(null)
const [containerHeight, setContainerHeight] = useState(0)
@@ -602,6 +616,7 @@ const FrontendPreviewPlayer: React.FC<FrontendPreviewPlayerProps> = ({
padding: "8px 12px",
boxShadow: "inset 0 0 0 16px transparent",
}}
ref={titleDragRef}
onPointerDown={handleTitlePointerDown}
onPointerMove={handleTitlePointerMove}
onPointerUp={handleTitlePointerUp}