diff --git a/src/renderer/features/lyrics/hooks/use-lyrics-animation-engine.ts b/src/renderer/features/lyrics/hooks/use-lyrics-animation-engine.ts index 073bd23dc..9d0aea64f 100644 --- a/src/renderer/features/lyrics/hooks/use-lyrics-animation-engine.ts +++ b/src/renderer/features/lyrics/hooks/use-lyrics-animation-engine.ts @@ -21,10 +21,14 @@ export interface UseLyricsAnimationEngineOptions { enabled?: boolean; followRef?: React.RefObject; followScrollAlignmentRef?: React.RefObject; + fontSize?: number; + gap?: number; lineIdPrefix: 'karaoke-line' | 'lyric'; lineLeadTimeMsRef?: React.RefObject; lyrics: SynchronizedLyrics; onLineActive?: (lineIndex: number) => void; + paddingLeft?: number; + paddingRight?: number; scrollContainerId: string; } @@ -34,10 +38,14 @@ export const useLyricsAnimationEngine = ({ enabled = true, followRef, followScrollAlignmentRef, + fontSize, + gap, lineIdPrefix, lineLeadTimeMsRef, lyrics, onLineActive, + paddingLeft, + paddingRight, scrollContainerId, }: UseLyricsAnimationEngineOptions) => { const internalAnimStateRef = useRef(createAnimEngineState()); @@ -83,6 +91,7 @@ export const useLyricsAnimationEngine = ({ const recalculatePositions = useCallback(() => { if (lyricsDataRef.current) { recalculateLinePositions(lyricsDataRef.current); + animStateRef.current.scroll.pendingScroll = true; animStateRef.current.scroll.wasUserScrolling = true; } // eslint-disable-next-line react-hooks/exhaustive-deps -- animStateRef is read via .current at call time @@ -149,20 +158,51 @@ export const useLyricsAnimationEngine = ({ }; }, [lyrics, enabled, rebuildLyricsData, reset]); + useEffect(() => { + const frame = requestAnimationFrame(() => { + recalculatePositions(); + }); + + return () => { + cancelAnimationFrame(frame); + }; + }, [fontSize, gap, paddingLeft, paddingRight, recalculatePositions]); + useEffect(() => { const container = containerRef.current; if (!container) { return; } + const observedElements = new Set(); + const observer = new ResizeObserver(() => { recalculatePositions(); }); - observer.observe(container); + const observeLayoutTargets = () => { + if (!observedElements.has(container)) { + observer.observe(container); + observedElements.add(container); + } + + const content = container.firstElementChild; + if (content && !observedElements.has(content)) { + observer.observe(content); + observedElements.add(content); + } + }; + + observeLayoutTargets(); + + const mutationObserver = new MutationObserver(() => { + observeLayoutTargets(); + }); + mutationObserver.observe(container, { childList: true }); return () => { observer.disconnect(); + mutationObserver.disconnect(); }; }, [containerRef, recalculatePositions]); diff --git a/src/renderer/features/lyrics/synchronized-karaoke-lyrics.tsx b/src/renderer/features/lyrics/synchronized-karaoke-lyrics.tsx index 001db744b..116119bae 100644 --- a/src/renderer/features/lyrics/synchronized-karaoke-lyrics.tsx +++ b/src/renderer/features/lyrics/synchronized-karaoke-lyrics.tsx @@ -94,9 +94,13 @@ export const SynchronizedKaraokeLyrics = ({ containerRef, followRef, followScrollAlignmentRef, + fontSize: settings.fontSize, + gap: settings.gap, lineIdPrefix: 'karaoke-line', lineLeadTimeMsRef, lyrics: normalizedLyrics, + paddingLeft: settings.paddingLeft, + paddingRight: settings.paddingRight, scrollContainerId: LYRICS_SCROLL_CONTAINER_ID, }); diff --git a/src/renderer/features/lyrics/synchronized-lyrics.tsx b/src/renderer/features/lyrics/synchronized-lyrics.tsx index 58d851cab..28d48180c 100644 --- a/src/renderer/features/lyrics/synchronized-lyrics.tsx +++ b/src/renderer/features/lyrics/synchronized-lyrics.tsx @@ -82,9 +82,13 @@ export const SynchronizedLyrics = ({ containerRef, followRef, followScrollAlignmentRef, + fontSize: settings.fontSize, + gap: settings.gap, lineIdPrefix: 'lyric', lineLeadTimeMsRef, lyrics: normalizedLyrics, + paddingLeft: settings.paddingLeft, + paddingRight: settings.paddingRight, scrollContainerId: LYRICS_SCROLL_CONTAINER_ID, });