와 useLayoutEffect cleanup을 조합하면 intersection observer 없이도 비디오 pause를 깔끔하게 처리할 수 있다.
import { useState, useCallback } from 'react';
import { useOnInView } from 'react-intersection-observer';
const [player, setPlayer] = useState<YouTubePlayer | null>(null);
const pauseVideo = useCallback(() => {
if (player) {
player.pauseVideo();
}
}, [player]);
const trackingRef = useOnInView(
(inView) => {
if (inView) {
// Element is in view - perhaps log an impression
} else {
pauseVideo();
}
},
{ threshold: 0.5, triggerOnce: true }
);
return (
<div ref={trackingRef} className={className}>
<YouTube ... />
</div>
);
import { Activity, useState, useLayoutEffect } from 'react';
import { useInView } from 'react-intersection-observer';
const [player, setPlayer] = useState<YouTubePlayer | null>(null);
const { ref, inView } = useInView({ threshold: 0.5 });
useLayoutEffect(() => {
return () => {
player?.pauseVideo();
};
}, [player]);
return (
<div ref={ref} className={className}>
<Activity mode={inView ? 'visible' : 'hidden'}>
<YouTube ... />
</Activity>
</div>
);
| 항목 | Before | After |
|---|---|---|
| 비디오 pause 방식 | useCallback + useOnInView 콜백에서 수동 호출 | useLayoutEffect cleanup 자동 실행 |
| 뷰포트 이탈 처리 | triggerOnce: true (1회만) | <Activity mode> 토글 (반복 동작) |
| DOM 상태 | 항상 렌더링 | hidden 시 display: none, 상태 보존 |
| Effect 관리 | 직접 관리 | Activity가 hidden 시 cleanup, visible 시 재생성 |
스크롤 아웃 → inView=false
→ <Activity mode="hidden">
→ display: none 적용
→ useLayoutEffect cleanup 실행 → player.pauseVideo()
스크롤 인 → inView=true
→ <Activity mode="visible">
→ display: none 제거, 이전 상태 복원
→ useLayoutEffect 재생성
<div ref={ref}>는 <Activity> 밖에 위치하여 intersection observer 센티넬 역할 유지useLayoutEffect를 사용하는 이유: 브라우저 paint 전에 동기적으로 실행되어 hidden 전환 시 오디오 누출 방지<Activity>는 DOM을 제거하지 않고 display: none으로 숨기므로 iframe 상태(버퍼링, 재생 위치)가 보존됨