[React] - FE 신입 포트폴리오 (준비 과정 기록용) | menubar편

ain·2022년 8월 28일
1

포트폴리오

목록 보기
2/4
post-thumbnail

헤더 바 변경


헤더 바는 볼 때마다 부자연스럽다고 생각해서 변경된 부분이 많다.
잠시 헤더 바의 역사를 보자면…

초기는 요랬다가↓

로고를 왼쪽에 넣으면서 오른쪽 위에는 목차, 가에는 Contact 아이콘을 붙였다.

또 그다음에는 로고 모양도 바꾸고, 헤더 바 자체도 떠 있는 것처럼 바꾸었고 목차도 한글로 바꾸었다.


하지만 화면을 줄이면 저 목차들이 로고와 겹쳐버려서 목차의 크기를 줄이거나 화면이 줄어들 때마다 메뉴바로 바뀌도록 구현해보았다.

그랬더니 메뉴바를 연 상태에서 화면 크기를 늘리면 메뉴바가 없어지는(...) 일이 발생한다. 이러면 곤란해지기 때문에 최종적으로는 목차를 없애고 Contact 아이콘들과 홈, mystory로 이동하는 링크를 메뉴바에 다 넣고, 헤더 바에는 로고와 메뉴바만 떠 있게 구현하였다.


메뉴바 만들기


1. 메뉴바 그려주기

  • 메뉴바 아이콘을 가져와 준다. 헤드 바에 있는 메뉴바 아이콘을 누르면 메뉴바가 툭 튀어나오는 것이기 때문에 아이콘은 헤드 바 컴포넌트에 넣어준다.

  • 메뉴바 컴포넌트를 따로 생성한 후 메뉴바를 그려준다.

// JSX
export function Menubar () {
return (
  <Menubar>
    <MenubarWrapper>
      <Menu>메뉴1</Menu>
      <Menu>메뉴2</Menu>
    </MenubarWrapper>
  </Menubar>
  )
};
//styled-components
export const Menubar = styled.div`
  display: block;
  position: fixed;
`;
export const MenubarWrapper = styled.div`
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  display: flex;
  flex-direction: column;
  width: 200px;
  padding: 24px;
  background-color: ${palette.bgColor};
`;

2. 메뉴바 상태 값 전역에 뿌려주기.

  • 이제 메뉴바가 실제로 동작하도록 상태 값을 넣어줘야 한다.
    나의 경우 메뉴바는 메뉴바 컴포넌트(Menubar.js) 에서도 필요했고, 헤드 바 컴포넌트(HeadBar.js)에서 아이콘을 클릭할 때도 필요했기 때문에 Context로 만들어준 다음 필요한 곳에서 가져다 썼다.
// store.js
import React, { createContext, useState } from 'react';
export const UserContext = createContext();
export const HeadBarContext = (props) => {
  const [menubar, setMenubar] = useState(false);
  const value = {
    menubar,
    open: (toggleMenu) => setMenubar(toggleMenu),
  };
  return (
    <UserContext.Provider value={value}>{props.children}</UserContext.Provider>
  );
};
// App.js
export default function App() {
  return (
    <HeadBarContext>
        <BrowserRouter>
          <Routes>
            <Route path={'/'} element={<Home />} />
          </Routes>
        </BrowserRouter>
    </HeadBarContext>
  );
}
  • 헤드 바 컴포넌트에서는 아이콘을 클릭했을 때 메뉴바가 열려야 하므로 아이콘에 onClick 이벤트를 걸어주고 Context에서 상태 값을 가져와서 메뉴바를 열어준다(false였던 상태 값을 true로 바꿔준다).
import React, { useState } from 'react'
export function HeadBar () {
const value = useContext(UserContext);
  const { menubar, open } = value;
  ...
  return(
    <Menubar onClick={() => {
        open(true);
      }}>
      메뉴바
    </Menubar>
  )
}

3. 이렇게 전역에 뿌려진 상태 값으로 더 해야 하는 일들은:


1. 메뉴바 모션
framer-motion으로 motion을 만들었고 메뉴바가 열릴 때와 닫힐 때의 motion을 삼항 연산자로 motion을 넣어주었다.

const variants = {
   open: { x: [150, 0, 0] },
   close: { x: 250 },
 };
...
<MenubarWrapper 
 animate={menubar ? 'open' : 'close'} 
 variants={variants}
</MenubarWrapper>

2. 스크롤 막기
메뉴바를 제외한 다른 화면을 클릭하면 메뉴바가 닫히게 만들기 위해 CSS로 메뉴바 뒤에 상하좌우로 꽉 찬 회색 컨테이너를 만든다.

export const Unscroll = styled(motion.div)`
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: -1;
  background-color: rgba(0, 0, 0, 0.5);
`;

그리고 메뉴바가 열리면 스크롤을 막아줘야 하는데 이때 body에 overflow: 'hidden'을 걸어주면 스크롤이 막히게 된다.

<Menubar onClick={() => {
    open(true);
    document.body.style.overflow = 'hidden'
}}>
  메뉴바
</Menubar>

3. 메뉴바 닫기
위에서 만든 스크롤을 막는 컨테이너를 클릭하면 메뉴바가 닫혀야 한다.
닫히려면 menubar 상태 값도 다시 false로 만들어 주고, 스크롤도 다시 가능케 해야 하므로 overflow: hidden도 null로 바꿔줌으로써 메뉴바가 열릴 때와는 상반된 함수를 써주면 된다.

여기서 주의해야 할 점은 메뉴바가 열리지 않은 상태에서는 이 컨테이너를 없애줘야 해서, 상태 값으로 컨트롤해준다.

{menubar ? (
    <>
      <Unscroll
        onClick={() => {
          open(false);
          document.body.style.overflow = null;
        }}
      />
    </>
  ) : null}

4. 페이지 이동
메뉴를 클릭해서 페이지가 이동하게 될 때, 메뉴바가 열려 있으면 이동하고 또 메뉴바를 닫아야 하는 불편함이 있으므로 메뉴를 클릭하면 메뉴바가 닫혀야 한다. 그러려면 메뉴가 클릭 될 때 상태 값을 다시 false로 돌려놓으면 된다.

<Menu
  to='/'
  onClick={() => {
    open(false);
    document.body.style.overflow = null;
  }}
  whileHover={{ color: palette.fontColor }}></Menu>


이슈


  • 메뉴바가 열리면 화면이 움직이는 이슈

    이렇게 메뉴바를 열면 요소들이 오른쪽으로 이동한다.
    헤드바 같이 화면 크기에 영향받지 않는 요소들은 제외하고, 화면 크기에 따라 반응형으로 움직이는 요소들만 오른쪽으로 이동한다.
    확실치는 않지만, 메뉴바가 열렸을 때 body에 overflow: hidden이 먹히기 때문에 오른쪽에 스크롤바가 사라지면서 body 전체가 움직이는 게 아닐까…. 추측해보았다.
    그래서 메뉴바가 열리면 화면 오른쪽에 margin 몇 픽셀을 줘보았더니 해결되었다.

profile
프론트엔드 개발 연습장 ✏️ 📗

0개의 댓글