페이지 전환 애니메이션

AnimatePresence 컴포넌트와 end 속성을 사용하면 컴포넌트가 언마운트 하기 전에 모션을 처리할 수 있어 페이지 라우트(route) 전환 시 모션을 적용할 수 있습니다.

그러기 위해선 경로의 전체 설정을 AnimatePresence 컴포넌트로 래핑해야 합니다.

import { AnimatePresence }from 'framer-motion';

function App() {
	return (
    <AnimatePresence>
      <Switch>
        <Route path="/" exact component={HomePage} />
        <Route path="/movies" component={MovieListPage} />
        <Route path="/movie/:id" component={MovieDetailPage} />
        <RouteGuard path="/bookmarks" component={BookmarkPage} />
        <Route path="/page-not-found" component={PageNotFound} />
        <Redirect to="/page-not-found" />
      </Switch>
    </AnimatePresence>
  )
}

이 설정만으로 경로 모션이 작동하지는 않습니다. 페이지가 언제 전환될 지 모르기 때문입니다. 그러므로 페이지가 전환 됨을 알 수 있도록 (location.pathname 변경) React Router로부터 useLocation 훅을 가져와 사용해야 합니다.

import { AnimatePresence }from 'framer-motion';
import { Switch, Route, Redirect, useLocation }from 'react-router-dom';

useLocation 훅으로부터 현재 페이지 경로 정보를 가져와 Switch 컴포넌트의 location 속성에 연결해 페이지가 언제 변경(언마운트) 되는 지 알려줘야 합니다. 그리고 key 속성 값으로 location.key를 설정해 React에 의해 재조정 될 수 있도록 설정합니다.

functionApp() {
	const location = useLocation();
	
	return (
    {/*
      exitBeforeEnter 속성을 설정하면 이전 페이지 모션이 완료된 이후,
      다음 페이지 모션이 시작 됨 (참고: <https://bit.ly/2R14lHt>)
    */}
    <AnimatePresence exitBeforeEnter>
      <Switch location={location} key={location.key}>
        <Route path="/" exact component={HomePage} />
        <Route path="/movies" component={MovieListPage} />
        <Route path="/movie/:id" component={MovieDetailPage} />
        <RouteGuard path="/bookmarks" component={BookmarkPage} />
        <Route path="/page-not-found" component={PageNotFound} />
        <Redirect to="/page-not-found" />
      </Switch>
    </AnimatePresence>
  );
}

이제 각 페이지 컴포넌트가 진입할 때와 나갈 때 모션을 정해주면 페이지 전환 간 모션이 적용됩니다.

pageVariants 설정을 모든 페이지에서 불러와 적용하면 페이지에 일관된 페이지 전환 모션을 설정할 수 있습니다. 다른 방법은 MotionPage 컴포넌트를 만들어 모든 페이지의 컨테이너로 사용하면 일괄적으로 페이지 전환 모션을 손쉽게 제어가 가능합니다.

import { motion }from 'framer-motion';

// 페이지 배리에이션
export const pageVariants = {
  hidden: { opacity: 0 },
  visible: { opacity: 1, transition: { delay: 0.5, duration: 0.5 } },
  slideOut: {
    x: '-100vw',
    transition: { ease: 'easeOut' }
  }
};

export default function HomePage({ history }) {
	return (
    {/* 페이지를 모션 요소로 만든 후, 배리에이션 상태 이름을 설정 */}
    <motion.div
      className="home-page"
      variants={pageVariants}
      initial="hidden"
      animate="visible"
      exit="slideOut"
    >
      <Helmet>
        <title>홈 ← "나의 영화" 서비스</title>
      </Helmet>
      <Effects message="ENTER" className={effect} />
        <a
        href="#go-to-movies"
        onClick={(e) => {
          e.preventDefault()
          history.push('/movies')
        }}
      >
        <img className={vision} src={image} alt="영화 목록 페이지로 이동" />
      </a>
    </motion.div>
  );
}

예제 분석