[React] Acitivity 컴포넌트 적용

JuHee Kim·2026년 3월 29일

와 useLayoutEffect cleanup을 조합하면 intersection observer 없이도 비디오 pause를 깔끔하게 처리할 수 있다.

Before

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>
);

After

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>
);

비교

항목BeforeAfter
비디오 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 상태(버퍼링, 재생 위치)가 보존됨

참고

profile
Frontend developer, interested in Data.

0개의 댓글