본문 바로가기
React

[React] IntersectionObserver로 스크롤 시 Fade-in 애니메이션 적용하기

by teamnova 2025. 7. 28.
728x90

 

안녕하세요 오늘은 React에서 IntersectionObserver API를 활용해 특정 컴포넌트가 화면에 진입할 때 부드럽게 나타나는 효과를 구현해보겠습니다.  React에서 scroll 이벤트 없이 간단하게 등장 애니메이션을 만드는 예제입니다.

 

IntersectionObserver란?

보통 스크롤에 반응하는 UI를 만들 때 scroll 이벤트를 사용하지만,
이 방법은 성능 저하, 여러 컴포넌트 관리의 복잡성 등의 문제가 생길 수 있습니다.

이런 문제를 해결해주는 게 바로 IntersectionObserver입니다.

"요소가 뷰포트에 들어왔는지"를 관찰해서 이벤트 없이도 실행을 제어할 수 있는 브라우저 API입니다.

 

 

먼저 FadeInSection 컴포넌트를 만들어줍니다. 

 

1. FadeInSection.jsx 

import { useEffect, useRef, useState } from "react";

const FadeInSection = ({ children }) => {
  const ref = useRef();
  const [isVisible, setVisible] = useState(false);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setVisible(true);
          observer.unobserve(ref.current); // 한 번만 작동
        }
      },
      { threshold: 0.2 }
    );
    observer.observe(ref.current);
    return () => observer.disconnect();
  }, []);

  return (
    <div
      ref={ref}
     className={`transition-all duration-[2000ms] ease-in-out transform p-6 bg-white dark:bg-gray-800 rounded shadow ${
       isVisible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-12"
      }`}
    >
      {children}
    </div>
  );
};

export default FadeInSection;

 

- entry.isIntersecting이 true가 되는 순간을 감지합니다. 

- observer.unobserve()를 사용해 한 번만 작동하게 설정했습니다. 페이지 최초 진입했을 때 한번만 실행됩니다. 

- Tailwind CSS로 애니메이션 적용하였습니다. 

 

 

2. App.jsx 

import Header from "./components/Header";
import Hero from "./components/Hero";
import About from "./components/About";
import Contact from "./components/Contact";
import ThreeDPortfolio from "./components/ThreeDPortfolio";
import AvatarPortfolio from "./components/SimpleAvatar";
import ProjectGallery from "./components/ProjectGallery";
import StatsSection from "./components/StatsSection";
import ServicesSection from "./components/ServicesSection";
import BlogSlider from "./components/BlogSlider";
import ParticlePortfolio from "./components/ParticleSystem";
import SimpleCounter from "./components/SimpleCounter";
import SimpleTodo from "./components/SimpleTodo";
import SimpleCalculator from "./components/SimpleCalculator";
import VideoBackground from "./components/VideoBackground";
import { motion, useScroll } from "motion/react";
import FadeInSection from "./components/FadeInSection";

function App() {
  const { scrollYProgress } = useScroll();

  return (
    <>
      <motion.div
        id="scroll-indicator"
        style={{
          scaleX: scrollYProgress,
          position: "fixed",
          top: 0,
          left: 0,
          right: 0,
          height: "10px",
          originX: 0,
          backgroundColor: "#ff0088",
          zIndex: 9999,
          transformOrigin: "0% 50%",
        }}
      />
      <div style={{ minHeight: "200vh" }}>
        {" "}
        {/* 충분한 스크롤을 위한 임시 높이 */}
        <VideoBackground />
        <Header />
        <Hero />
        <FadeInSection>
          <About />
        </FadeInSection>
        <FadeInSection>
          <ServicesSection />
        </FadeInSection>
        <FadeInSection>
          <StatsSection />
        </FadeInSection>
        <ProjectGallery />
        <BlogSlider />
        <SimpleCounter />
        <SimpleTodo />
        <SimpleCalculator />
        <ThreeDPortfolio />
        <AvatarPortfolio />
        <ParticlePortfolio />
        <Contact />
      </div>
    </>
  );
}

export default App;

- 메인 컴포넌트 파일인 App.jsx 에 FadeInSection 파일을 import 해줍니다. 

- 원하는 컴포넌트를 감싸줍니다. 

- 복잡한 scroll 이벤트 제어나 클래스 토글 없이, 상태와 observer가 내부에서 자동으로 처리됩니다.

 

 

 

 

시연 영상입니다. 

  <FadeInSection> 으로 감싼 컴포넌트가 부드럽게 fade in 되는 것을 확인할 수 있습니다.