properly handle settings state on lyrics follow

This commit is contained in:
jeffvli
2026-07-09 03:59:20 -07:00
parent 04258061e2
commit c0c03bc414
3 changed files with 49 additions and 1 deletions
@@ -21,10 +21,14 @@ export interface UseLyricsAnimationEngineOptions {
enabled?: boolean; enabled?: boolean;
followRef?: React.RefObject<boolean>; followRef?: React.RefObject<boolean>;
followScrollAlignmentRef?: React.RefObject<number>; followScrollAlignmentRef?: React.RefObject<number>;
fontSize?: number;
gap?: number;
lineIdPrefix: 'karaoke-line' | 'lyric'; lineIdPrefix: 'karaoke-line' | 'lyric';
lineLeadTimeMsRef?: React.RefObject<number>; lineLeadTimeMsRef?: React.RefObject<number>;
lyrics: SynchronizedLyrics; lyrics: SynchronizedLyrics;
onLineActive?: (lineIndex: number) => void; onLineActive?: (lineIndex: number) => void;
paddingLeft?: number;
paddingRight?: number;
scrollContainerId: string; scrollContainerId: string;
} }
@@ -34,10 +38,14 @@ export const useLyricsAnimationEngine = ({
enabled = true, enabled = true,
followRef, followRef,
followScrollAlignmentRef, followScrollAlignmentRef,
fontSize,
gap,
lineIdPrefix, lineIdPrefix,
lineLeadTimeMsRef, lineLeadTimeMsRef,
lyrics, lyrics,
onLineActive, onLineActive,
paddingLeft,
paddingRight,
scrollContainerId, scrollContainerId,
}: UseLyricsAnimationEngineOptions) => { }: UseLyricsAnimationEngineOptions) => {
const internalAnimStateRef = useRef<AnimEngineState>(createAnimEngineState()); const internalAnimStateRef = useRef<AnimEngineState>(createAnimEngineState());
@@ -83,6 +91,7 @@ export const useLyricsAnimationEngine = ({
const recalculatePositions = useCallback(() => { const recalculatePositions = useCallback(() => {
if (lyricsDataRef.current) { if (lyricsDataRef.current) {
recalculateLinePositions(lyricsDataRef.current); recalculateLinePositions(lyricsDataRef.current);
animStateRef.current.scroll.pendingScroll = true;
animStateRef.current.scroll.wasUserScrolling = true; animStateRef.current.scroll.wasUserScrolling = true;
} }
// eslint-disable-next-line react-hooks/exhaustive-deps -- animStateRef is read via .current at call time // 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]); }, [lyrics, enabled, rebuildLyricsData, reset]);
useEffect(() => {
const frame = requestAnimationFrame(() => {
recalculatePositions();
});
return () => {
cancelAnimationFrame(frame);
};
}, [fontSize, gap, paddingLeft, paddingRight, recalculatePositions]);
useEffect(() => { useEffect(() => {
const container = containerRef.current; const container = containerRef.current;
if (!container) { if (!container) {
return; return;
} }
const observedElements = new Set<Element>();
const observer = new ResizeObserver(() => { const observer = new ResizeObserver(() => {
recalculatePositions(); 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 () => { return () => {
observer.disconnect(); observer.disconnect();
mutationObserver.disconnect();
}; };
}, [containerRef, recalculatePositions]); }, [containerRef, recalculatePositions]);
@@ -94,9 +94,13 @@ export const SynchronizedKaraokeLyrics = ({
containerRef, containerRef,
followRef, followRef,
followScrollAlignmentRef, followScrollAlignmentRef,
fontSize: settings.fontSize,
gap: settings.gap,
lineIdPrefix: 'karaoke-line', lineIdPrefix: 'karaoke-line',
lineLeadTimeMsRef, lineLeadTimeMsRef,
lyrics: normalizedLyrics, lyrics: normalizedLyrics,
paddingLeft: settings.paddingLeft,
paddingRight: settings.paddingRight,
scrollContainerId: LYRICS_SCROLL_CONTAINER_ID, scrollContainerId: LYRICS_SCROLL_CONTAINER_ID,
}); });
@@ -82,9 +82,13 @@ export const SynchronizedLyrics = ({
containerRef, containerRef,
followRef, followRef,
followScrollAlignmentRef, followScrollAlignmentRef,
fontSize: settings.fontSize,
gap: settings.gap,
lineIdPrefix: 'lyric', lineIdPrefix: 'lyric',
lineLeadTimeMsRef, lineLeadTimeMsRef,
lyrics: normalizedLyrics, lyrics: normalizedLyrics,
paddingLeft: settings.paddingLeft,
paddingRight: settings.paddingRight,
scrollContainerId: LYRICS_SCROLL_CONTAINER_ID, scrollContainerId: LYRICS_SCROLL_CONTAINER_ID,
}); });