본문 바로가기
Develog/Front

[우아한-Tech 스터디] Framer-motion을 이용한 애니메이션으로 화려한 웹 페이지 만들기 - 2주차 -

by 예 강 2024. 6. 20.

 

 

만들고자 하는 트랜지션 효과

 

완성품

 

개요

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 컴포넌트의 콤비네이션이 핵심이라고 할 수 있다.