본문 바로가기
카테고리 없음

페이지 간 전환 효과 부여

by 잘먹는 개발자 에단 2025. 4. 25.

## 작동 순서

1. 초기 렌더링

- 앱이 시작될 때 HashRouter -> PageTransition -> Routes -> Home 순으로 컴포넌트가 마운트 된다.

- TransitionContainer ( == PageTransition 내부의 styled.div ) 는 마운트 되면서 animation : slideIn 0.5s가 자동으로 실행된다.

- 화면에 Home 페이지가 오른쪽 바깥에서 왼쪽으로 슬라이드 인 되면서 나타남

 

2. 페이지 이동 ( 언마운트 트리거 )

- 사용자가 #/today 링크를 클릭하면 useLocation()이 새 경로를 감지한다.

- location.pathname 이 바뀌면 React는 key={location.pathname} 이 달린 TransitionContainer를 

    1) 기존 엘리먼트 언마운트 ( 제거 ) 

    2) 새 엘리먼트 마운트 ( 생성 ) 순으로 처리하려고 준비한다.

 

3. 퇴장 애니메이션 실행

- 우리의 useEffect가 location.pathname 변화를 감지해서 

setIsExiting(true);
setTimeout(() => setIsExiting(false), 500);

 

이렇게 isExiting을 true로 바꾸고

- className="slide-out"이 붙으면 css slideout 키 프레임이 0.5초동안 실행되면서 페이지가 왼쪽으로 빠져나가듯 사라진다.

 

4. 실제 언마운트 & 새 마운트

- 0.5 초 뒤 ( setTime 후 ) isExiting = false로 리셋되고, 리액트는

    ㄴ 기존 TransitionContainer를 실제로 DOM에서 제거하고 ( 언마운트 )

    ㄴ key가 바뀐 새로운 TransitionContainer를 다시 생성한다. ( 마운트 )

- 새로 마운트 될 때는 className에 slide-out이 없으니, 기본 animation : slideIn이 다시 붙어 오른쪽에서 슬라이드 인

 

5. 새 페이지 완전 노출

- 슬라이드 인 애니메이션 0.5초이 끝나면 화면에 최종적으로 Today 컴포넌트가 고정된다. 

 

## 애니메이션 시각화

[ Home 화면 노출 ]      --(링크 클릭)-->
[ Home 화면 slide-out ] --(0.5s)-->
[ Home 언마운트 & Today 마운트 ] --(자동 slide-in)-->
[ Today 화면 노출 ]

 

왜 이렇게 짰냐면?

  • key 프로퍼티
    • React에서 같은 타입 컴포넌트라도 key가 바뀌면 “새 컴포넌트”로 취급해서 언마운트→마운트 과정을 거쳐.
  • useEffect + 상태(isExiting)
    • 경로가 바뀌는 순간 퇴장 애니메이션을 주기 위해, 상태를 토글해서 .slide-out 클래스를 붙였다 뗐다 함.
  • CSS 애니메이션
    • styled.div에 @keyframes 두 개를 정의해 두고, 클래스 토글만으로 입·퇴장 모두 처리.

이 구조 덕분에 각 페이지 컴포넌트는 전환 로직 신경 안 쓰고, PageTransition만 감싸주면 알아서 슬라이드 효과가 적용돼

 

 

// ▶ 리액트, 훅, 스타일러 불러오기
import React, { useEffect, useState } from "react";          // React 및 훅
import { useLocation } from "react-router";                  // 현재 경로 얻는 훅
import styled from "@emotion/styled";                        // emotion styled

// ▶ Props 타입 정의
interface PageTransitionProps {
  children: React.ReactNode;  // 감싸질 페이지 컴포넌트들
}

// ▶ PageTransition 컴포넌트
const PageTransition: React.FC<PageTransitionProps> = ({ children }) => {
  const location = useLocation();            // ① 현재 URL 경로 가져오기
  const [isExiting, setIsExiting] = useState(false); // ② 퇴장 애니메이션 상태

  useEffect(() => {
    // 경로가 바뀌면 퇴장 애니메이션 트리거
    setIsExiting(true);
    const timeout = setTimeout(() => {
      // 애니메이션 끝나면 진입 상태로 리셋
      setIsExiting(false);
    }, 500); // 애니메이션 지속시간(ms)
    return () => clearTimeout(timeout);      // 언마운트 시 타임아웃 정리
  }, [location.pathname]);                   // 경로가 바뀔 때마다 실행

  return (
    <TransitionContainer
      key={location.pathname}                // ③ key가 바뀌면 React가 새로 마운트/언마운트
      className={isExiting ? "slide-out" : ""} // ④ isExiting에 따라 퇴장 클래스 토글
    >
      {children}                             // ⑤ 실제 페이지 내용 렌더
    </TransitionContainer>
  );
};

// ▶ 애니메이션 스타일 정의
const TransitionContainer = styled.div`
  position: absolute;   /* 페이지를 서로 겹치게 함 */
  top: 0; left: 0;
  width: 100%;
  height: 100%;
  animation: slideIn 0.5s ease-in-out forwards;  /* 기본 진입 애니메이션 */

  &.slide-out {
    animation: slideOut 0.5s ease-in-out forwards; /* 퇴장 애니메이션 */
  }

  /* 진입 애니메이션 키프레임 */
  @keyframes slideIn {
    from { transform: translateX(100%); } /* 오른쪽 바깥에서 시작 */
    to   { transform: translateX(0); }    /* 제자리로 이동 */
  }
  /* 퇴장 애니메이션 키프레임 */
  @keyframes slideOut {
    from { transform: translateX(0); }      /* 제자리에서 */
    to   { transform: translateX(-100%); }  /* 왼쪽 바깥으로 나감 */
  }
`;

export default PageTransition; // 사용하려면 export

 

 

// ▶ 리액트와 라우터 컴포넌트 불러오기
import React from "react";
import { HashRouter, Routes, Route } from "react-router";  

// ▶ 페이지·스타일·애니메이션 컴포넌트
import Home from "./pages/Home";                      
import Today from "./pages/Today";                    
import "./styles/global/global.css";                
import PageTransition from "./components/animation/PageTransition";

function App() {
  return (
    <HashRouter>               {/* # 기반 라우터로 감싸기 */}
      <PageTransition>         {/* 여기 안의 페이지들에 애니메이션 적용 */}
        <Routes>               {/* 경로별 렌더되는 컴포넌트 설정 */}
          <Route path="/" element={<Home />} />      
          <Route path="/today" element={<Today />} />
        </Routes>
      </PageTransition>
    </HashRouter>
  );
}

export default App;          // 외부에서 사용 가능하게 export