오늘은 리액트와 리액트 라우터를 복습해보면서 기존 Framer-motion을 사용해 구현했던 페이지 이동 애니메이션을 구현해보았습니다. 페이지 이동 애니메이션이나 컴포넌트 마운트, 언마운트 애니메이션으로 부를 수 있겠습니다.
리액트는 가상돔을 사용해 화면을 빠르고 부드럽게 렌더링해주는 장점이 있는데요. 리액트 라우터를 사용한다면 전체 페이지 이동간 깜빡임과 전환 속도는 눈깜짝할 사이에 바뀝니다. 물론 빠른것도 좋지만, 페이지가 전환될 타이밍에 약간의 애니메이션 효과를 줘서 꾸며줄 수 있습니다.
상단 네비게이션으로 페이지를 이동하는데, 이동할 때마다 슬라이드 업/다운 효과를 내줍니다. 기존 리액트 라우터가 v6로 업데이트 되면서 사용하던 컴포넌트가 조금 달려졌습니다.
App.js
import './App.css'
import { Routes, Route } from 'react-router-dom'
import NavBar from './components/NavBar'
import Home from './pages/Home'
import Page1 from './pages/Page1'
import Page2 from './pages/Page2'
import Page3 from './pages/Page3'
import { useRef } from 'react'
import { useNavigate, useLocation } from "react-router-dom"
const menu = [
{ title : 'Home', link : '/' },
{ title : 'Page1', link : 'page1' },
{ title : 'Page2', link : 'page2' },
{ title : 'Page3', link : 'page3' },
];
function App() {
const navigate = useNavigate();
const { pathname } = useLocation();
const wrapRef = useRef(null);
const movePage = (url) =>{
if(pathname !== `/${url}` ){
wrapRef.current.classList.replace('loaded', 'unloaded');
setTimeout(()=> {
navigate(url);
wrapRef.current.classList.replace('unloaded', 'loaded');
} , 390)
}
}
return (
<div className="App">
<NavBar menu={menu} movePage={movePage} />
<div ref={wrapRef} className="wrap loaded">
<Routes>
<Route exact={true} path='/' element={<Home/>} />
<Route exact={true} path='/page1' element={<Page1/>} />
<Route exact={true} path='/page2' element={<Page2/>} />
<Route exact={true} path='/page3' element={<Page3/>} />
</Routes>
</div>
</div>
);
}
export default App
리액트 라우터에서 페이지 이동시 useHistory()를 썻던 기억이 나는데, useNavigate()로 변경됐고, <Switch> 컴포넌트는 <Routes>로 바뀌었습니다. 상단 네비게이션인 <NavBar/> 컴포넌트는 메뉴 정보가 들어있는 menu 객체 배열과 페이지 이동을 위한 movePage() 함수를 props로 전달해줍니다.
NavBar.js
import React from 'react';
const NavBar = ({menu, movePage}) => {
return (
<nav>
<ul className='nav'>
{ menu.map((m, idx)=> <li key={m.title+idx}><button onClick={()=> movePage(m.link)}>{m.title}</button></li>) }
</ul>
</nav>
);
};
export default NavBar
<NavBar/> 컴포넌트에서는 기존의 리액트 라우터의 <Link/> 컴포넌트를 사용하는 대신에 movePage라는 함수로 이동을 시켜줍니다.
const movePage = (url) =>{
if(pathname !== `/${url}` ){
wrapRef.current.classList.replace('loaded', 'unloaded');
setTimeout(()=> {
navigate(url);
wrapRef.current.classList.replace('unloaded', 'loaded');
} , 390)
}
}
해당 함수는 이동될 url을 파라미터로 전달받고 원하는 Dom을 지정해 class를 바꿔줍니다. 그리고 0.39s 후 페이지를 이동시켜주고 class 명을 다시 바꿔주면 완료됩니다.
loaded와 unloaded는 CSS 애니메이션 속성이 지정되어 있는데요. 아래와 같습니다.
App.css
.App {
text-align: center;
}
/* Mount & UnMount Effect Animations */
.loaded{
animation: 0.4s ease load forwards;
}
.unloaded{
animation: 0.4s ease unload forwards;
}
@keyframes load{
0%{
opacity: 0;
transform: translateY(20px);
}
100%{
opacity: 1;
transform: translateY(0px);
}
}
@keyframes unload{
0%{
opacity: 1;
transform: translateY(0px);
}
100%{
opacity: 0;
transform: translateY(20px);
}
}
.wrap{
opacity: 0;
}
.home, .page1, .page2, .page3{
display: flex;
justify-content: center;
align-items: center;
min-height: 500px;
font-size: 2rem;
}
.home{ background: rgb(86, 152, 238); }
.page1{ background: #b67e7e; }
.page2{ background: rgb(77, 77, 77); }
.page3{ background: rgb(199, 255, 199); }
.nav{
display: flex;
justify-content: center;
align-items: center;
}
.nav>li{
display: inline-block;
padding: 0 1em;
}
.nav>li>button{
border: none;
background: none;
outline: 0;
cursor: pointer;
font-size: 1rem;
}
효과를 줄 엘리먼트에 opcity 속성을 0으로 줘야 깜빡거림이 사라집니다.