개요
Framer-mortion 으로 페이지 전환시의 효과를 구현한다.
사용 개념
motion.div의 initial ,animate, exit 를 이용했다. + react-router-dom-v6 도
준비물
motion.div, AnimatePresense, react-router-dom-v6, useLocation
STEP 1
경로에 맞는 페이지 만들기
import { AnimatePresence, motion } from "framer-motion";
import { useLocation } from "react-router-dom";
const About = () => {
const location = useLocation();
return (
<AnimatePresence mode="wait">
<motion.div
style={{
backgroundColor: "red",
width: "100%",
height: "100%",
}}
key={location.pathname}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 1,delay:1 }}
>
<div
style={{
display: "flex",
justifyContent: "center",
gap: "10px",
}}
></div>
<div>About</div>
</motion.div>
</AnimatePresence>
);
};
export default About;
이러한 페이지를 4개 만들어 주십시오
STEP2 라우터 만들어주기
export const router = createBrowserRouter([
{
path: "/",
element: <App></App>,
children: [
{
path: "about",
element: <About></About>,
},
{
path: "home",
element: <Home></Home>,
},
{
path: "tanggu",
element: <Tanggu></Tanggu>,
},
{
path: "main",
element: <Main></Main>,
},
],
},
]);
예제는 많은분들이 그냥 Route를 사용하셔서 자료를 찾기 쉽지 않았습니다.
혹시 예제를 따라했는데 안된다 싶은 분들은 라우터쓰는 방식이 다르지 않은가 확인해 보세요!
import React from "react";
import ReactDOM from "react-dom/client";
import { router } from "./App.tsx";
import "./index.css";
import { RouterProvider, } from "react-router-dom";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<RouterProvider
router={router}
></RouterProvider>
</React.StrictMode>
);
라우터 설정을 해줍니다.
STEP3 애니메이션 넣기 (중요)
import "./App.css";
import {
Link,
Outlet,
createBrowserRouter,
useLocation,
} from "react-router-dom";
import { AnimatePresence, motion } from "framer-motion";
import Tanggu from "./pages/Tanggu";
import Home from "./pages/Home";
import Main from "./pages/Main";
import About from "./pages/About";
export const router = createBrowserRouter([
{
path: "/",
element: <App></App>,
children: [
{
path: "about",
element: <About></About>,
},
{
path: "home",
element: <Home></Home>,
},
{
path: "tanggu",
element: <Tanggu></Tanggu>,
},
{
path: "main",
element: <Main></Main>,
},
],
},
]);
function App() {
const location = useLocation();
return (
<>
<Link to="/about">About Us</Link>
<Link to="/home">Home</Link>
<Link to="/tanggu">Tanggu</Link>
<Link to="/main">Main</Link>
<AnimatePresence mode="wait">
<motion.div
key={location.pathname} //key 값을 주셔야 페이지 전환을 인식한다고 합니다.
className="slide-in"
initial={{ scaleY: 0 }} //mount 시 0(initial) => 0(animate) 이므로 보이지 않습니다.
animate={{
scaleY: 0, //unmount 시 0(animate) => 1(exit)으로 변합니다.
transformOrigin: "bottom", //아래 방향에서 올라옵니다
transition: { duration: 1, ease: [0.22, 1, 0.36, 1] },
}}
exit={{
scaleY: 1,
transition: { duration: 1, ease: [0.22, 1, 0.36, 1] },
}}
style={{
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
width: "100vw",
height: "100vh",
transformOrigin: "bottom",
backgroundColor: "black",
zIndex: 1,
}}
/>
<motion.div
key={location.pathname} //key 값을 주셔야 페이지 전환을 인식합니다.
className="slide-out"
initial={{ scaleY: 1 }} //mount 시 1(initial) => 0(animate) 으로 변화합니다.
animate={{
scaleY: 0,
transition: {
duration: 1,
ease: [0.22, 1, 0.36, 1],
delay: 1,
transformOrigin: "top",
},
}}
exit={{ //unmount 시 0(animate) => 0(exit)이므로 보이지 않습니다.
scaleY: 0,
transition: {
duration: 1,
ease: [0.22, 1, 0.36, 1],
transformOrigin: "top", //위에서 효과가 시작됩니다.
},
}}
style={{
position: "fixed",
top: 0,
left: 0,
width: "100vw",
height: "100vh",
transformOrigin: "top",
backgroundColor: "black",
zIndex: 1,
}}
/>
</AnimatePresence>
<Outlet key={location.pathname} />
{/* //Outlet이 AnimatePresense안에 있으면 동작을 예측할 수 없습니다.
AnimatePresense 는 애니메이션 상태를 추적하고 자식컴포넌트의 마운트, 언마운트시
애니메이션을 동작시키는데 Outlet을 내부에 넣으면 제대로 자식컴포넌트를 인식하지 못하는것 같습니다. */}
</>
);
}
export default App;
겪은 이슈들
ISSUE1
앱 라우터에 대한 이해도 부족으로 라우터 적용에 어려움을 겪었다.
next.js 13버전이 되면서 기존의 예제 샘플들을 적응하는데 어려웠다. Outlet을 적용시키기 위해 App컴포넌트를 Root로 만들어주고 children 속성으로 다른 컴포넌트와 경로를 설정해서 해결할 수 있었다.
ISSUE2
AnimatePresense 안에 Outlet을 넣을경우 내가 원하는 대로 애니메이션 순서가 적용이 안되는 이슈가 있었다.
⇒ 순차적으로 마운트 되기 때문에 애니메이션의 마운트, 언마운트를 감지하는 부분이 꼬이는듯 한다. Outlet을 밖으로 빼내어 해결했다.
ISSUE3
페이지 전환이 너무 빠르게되어 페이지 넘김 효과가 적용되기 전에 페이지가 이동되었다.
⇒ 기본 페이지에 transition:{delay:1} 을 줘서 마운트가 천천히 되도록 수정했다.
소감
헤매다가 용기있게 스터디원에게 물어봤는데 도와주셔서 잘 해결할 수 있었다 ㅠㅠ
마운트 언마운트를 시키는 두개의 motion.div 컴포넌트의 콤비네이션이 핵심이라고 할 수 있다.