본문 바로가기
Frontend/Framer Motion

[Framer Motion] 메뉴바 애니메이션

by Rayched 2025. 8. 5.

0. 개요

 

이번에 다루는 것은 framer-motion을 활용해서 위와 같은 메뉴바를 구현하는 방법이다.

지금 만들고 있는 내 포트폴리오의 Header에 이를 적용하기 위해서 구현 방법을 공부해봤고

 

실제로 이를 구현하는데 성공하였고, 나중을 위해서 구현하는 과정을 블로그에 정리해두기로 하였다.


1. 구현 과정

예제 코드와 실행 예시는 다음과 같다.

 

import {styled} from "styled-components";
import {motion} from "framer-motion";
import {useState} from "react";

interface I_ItemData {
    ItemId: string;
    ItemNm: string;
}

const Wrapper = styled.div`
    width: 100vw;
    height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    background: linear-gradient(135deg, rgb(9, 132, 227), rgb(116, 185, 255));
`;

const MenuBox = styled.div`
    display: flex;
    flex-direction: row;
    align-items: center;
    background-color: white;
    border-radius: 15px;
    width: 30em;
    height: 5em;
`;

const MenuItem = styled.div`
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    position: relative;
    width: 7em;
    margin: 0px 3px;
`;

const MenuText = styled.div`
    font-size: 17px;
    font-weight: bold;
`;

const FocusBar = styled(motion.div)`
    width: 5em;
    height: 0.4em;
    background-color: black;
    border-radius: 15px;
    position: absolute;
    bottom: -18px;
`;

export default function App() {
    const [isMatch, setMatch] = useState("");

    const ItemData: I_ItemData[] = [
        { ItemId: "home", ItemNm: "홈" },
        { ItemId: "series", ItemNm: "시리즈" },
        { ItemId: "movies", ItemNm: "영화" },
        { ItemId: "bests", ItemNm: "인기 컨텐츠" },
    ];

    return (
        <Wrapper>
            <MenuBox>
                {
                    ItemData.map((data) => {
                        return (
                            <MenuItem key={data.ItemId} onClick={() => setMatch(data.ItemId)}>
                                <MenuText>{data.ItemNm}</MenuText>
                                {data.ItemId === isMatch ? <FocusBar /> : null}
                            </MenuItem>
                        );
                    })
                }
            </MenuBox>
        </Wrapper>
    );
};

애니메이션 효과 적용 전

 

각기 다른 ItemId와 ItemNm을 가진 <MenuItem /> 컴포넌트 4개를 랜더링한다.

여기서 <MenuItem /> 컴포넌트는 <FocusBar />라는 하위 컴포넌트를 가지고 있는데

해당 컴포넌트는 사용자의 현재 위치, URL 값을 기억하는 상태 'isMatch'의 값과

ItemId의 값이 일치하는 경우에만 랜더링되는 컴포넌트이다.

 

즉, <FocusBar />가 랜더링된 위치가 사용자가 현재 있는 위치를 가리킨다는 것이다.

(position: absolute로 설정하여, 요소 흐름에 영향을 받지 않게 하였음)

 

이제 <FocusBar />가 움직이는 애니메이션을 추가해야 한다.

 

FocusBar가 생성될 때, 삭제될 때의 애니메이션을 따로 구현할 필요는 없다.

그냥 FocusBar에 'layoutId'를 추가하는 것으로 해결된다.


/** 기존 코드 */

export default function App() {
	/** 기존 코드 */
    return (
        <Wrapper>
            <MenuBox>
                {
                    ItemData.map((data) => {
                        return (
                            <MenuItem key={data.ItemId} onClick={() => setMatch(data.ItemId)}>
                                    <MenuText>{data.ItemNm}</MenuText>
                                    {
                                    	data.ItemId === isMatch ? (
                                    		<FocusBar layoutId="FocusBar"/> 
                                    	): null
                                    }
                            </MenuItem>
                        );
                    })
                }
            </MenuBox>
        </Wrapper>
    );
};

 

<FocusBar />는 동일한 layoutId를 가지고 있기 때문에 해당 컴포넌트가 삭제 및 재생성될 때

애니메이션 효과가 자동으로 설정된 것을 위의 이미지를 통해서 확인할 수 있다.

 

CodeSandbox 예제 (HTML)

3. Reference

react framer-motion 메뉴바 / velog 해적왕
Layout Animation (framer-motion 노트)

'Frontend > Framer Motion' 카테고리의 다른 글

[Framer-motion] Drag and Drop  (0) 2025.04.30
Framer Motion 맛보기  (0) 2025.01.01