밀리고 밀린 프로젝트 정리를 빠르게 정리하기 위해 오늘은 개발을 하루 쉬어가고 그동안에 했던 작업들을 정리하는 시간을 가져볼 것이다.
일단 UI 구현은 구상까지해서 2주정도 걸린 것 같다. 이번에 목표로 했던 건 반응형 웹 + 군더더기 없는 UI를 구현하고 싶어서 UI 구상에 시간을 오래 쓴 것 같다. 최종적으로 완성된 UI랑은 다소 차이가 있지만.. 튼 이번 게시글에서는 UI 중에서도 header와 sidebar를 구현한 방법에 대해서 정리하고 가겠다.
최종적으로 구현한 결과물이다. 반응형에 따른 header와 sidebar를 보여주기 위해 3가지 반응형을 모두 담았다. 보통 반응형을 3가지 또는 4가지 분기점을 두게 되는데 현재는 3가지 분기점으로 진행했다.
PC : 1024px ~
태블릿 가로, 세로 : 768px ~ 1023px
모바일 가로, 세로 : ~768px
너비가 480px미만이 되면 원하던 결과물은 또 아니게 되는지라.. 480px를 구현할지.... 고민을 아직도 하고 있다. (반응형 진짜 상상 이상으로 너무 힘들었던 작업이기에...)
UI에 벌써 2주나 들어갔기 때문에 일단 현재는 3분기점에서 진행해보고 프로젝트가 일찍 끝나게 되면 그 때 추가적으로 구현을 해보려고 한다..
일단 header와 sidebar에 구현해야 하는 기능 또는 특징을 정리해보자면,
일단 3가지 분기점에서 header와 sidebar의 특징을 정리하자면 검색창은 pc와 태블릿에서만 나타난다. 즉, 모바일에서는 검색 기능을 사용 할 수가 없다. 추후 모바일에 검색 기능을 추가할 가능성을 긍정적으로 생각하고 있긴하다. 또 모바일에서는 sidebar가 나타나지 않는다. 모바일에서 sidebar를 표시하기에는 화면이 너무나도 좁기 때문에 어쩔 수 없이 sidebar를 사용하지 않고, header에 메뉴를 모바일에서만 나타나게 하여 메뉴를 클릭했을 때 드롭박스로 sidebar에 메뉴를 표시할 수 있도록 하였다.
분기점말고도 header와 sidebar의 특징은 로그인 등과 같은 특정 페이지에서는 표시하지 않도록 하였다.
로그인, 회원가입, 계정 찾기, 본인 인증, 비밀번호 재설정에서는 header와 sidebar를 표시하지 않았다. header와 sidebar에서 표시되는 항목(메뉴, 검색창, 로그인, 프로필이미지)는 필요한 항목이 아니였기 때문에 표시할 필요가 없다고 생각했다. 대신, main화면으로 돌아갈 수 있게 하기 위해 로고와 하단에 돌아가기 버튼을 통해 main으로 돌아갈 수 있는 방법을 추가해두었다.
서술한 기능들을 어떻게 구현했는지 지금부터 정리하도록 하겠다.
※ 해당 코드들은 기능이 전혀 구현되어있지 않습니다. 이후 기능 구현과 함께 수정될 수 있습니다.
header 컴포넌트를 만든 뒤, React Router를 사용해주었다.
React Router에 대한 설명은 이전 프로젝트 회고에서 하였으므로, 해당 게시글에서는 생략하여 설명한다.
<BrowserRouter>
<Header />
<Center>
<Sidebar />
<Routes>
<Route index element={<Main />} />
*다른 페이지들은 생략*
</Routes>
</Center>
</BrowserRouter>
<BrowserRouter> 사이에 <Header>와 <Center>를 추가해주면 된다. 여기서 사용한 Center는 Sidebar를 표시하기 위해 추가한 빈 div이다. 이후 sidebar에서 설명할 것이다.
const Wrapper = styled.div`
position: sticky;
top: 0;
z-index: 999;
*다른 속성들은 생략*
`;
header는 position: sticky를 사용해 상단에 고정시켜주었다. z-index를 999로 설정하여 페이지가 화면보다 클 경우, 위로 스크롤이 올라갈 때 header가 가려지는 것을 막아주어 페이지에 항상 위에 위치할 수 있도록 해주었다.
해당 검색창은 화면의 너비에 따라 계속 줄어드는 형태이다. flex-grow: 1을 통해 화면 너비에 맞춰서 줄어들고 커지도록 했다.
<SearchBox>
<CiSearch size="20" color="#a49f9f" />
<Search type="text" placeholder="검색"></Search>
</SearchBox>
SearchBox는 div요소로 검색창에서 회색 Box에 해당된다. 회색 Box 안에 row방향으로 아이콘과 input을 추가해주어 검색창을 만들어주었다.
CiSearch는 React Icons이며, 이에 대한 설명은 이전 게시글에 자세하게 설명해두었다.
<LoginBox to="/login">
{isLogin ? <Login size="23" color="#84828a" /> : <Logout size="23" color="#84828a" />}
{isLogin ? '로그아웃' : '로그인'}
</LoginBox>
LoginBox에는 isLogin 값에 따라 두 가지로 표현이 된다. LoginBox를 클릭할 경우 Link로 인해 로그인 페이지로 이동하게 해두었다. Login과 Logout은 모두 React Icons이다.
모바일 사이즈에서만 프로필 이미지 옆에 메뉴가 표시되게 된다. 해당 메뉴를 클릭하면 드롭박스 형태로 메뉴들이 표시되게 된다. 이를 이용해 모바일에서 sidebar가 없이 메뉴를 사용할 수 있도록 하였다.
const Menu = styled.div`
*이 외에 속성은 생략*
display: none;
/* 모바일 가로, 모바일 세로*/
@media all and (max-width: 767px) {
display: inline;
}
`;
기본 속성 값은 display: none이기에 평소화면에서는 메뉴가 표시되지 않는다. 하지만, 767px이하가 되면 display: inline으로 인해 표시가 되도록 해주었다.
※ opacity가 아닌 display로 속성을 추가해주어야 한다. 화면에 표시 안 되는 것은 똑같지만, opacity는 자리를 차지하면서 표시되지 않지만, display는 자리도 차지하지 않기 때문이다. 메뉴가 없을 땐 프로필 이미지가 화면 오른쪽에 붙어야 한다.
메뉴를 클릭했을 때 드롭박스는 useState를 사용해주었다. 메뉴를 클릭했을 때 useState 변수를 true로 바꾸어주고, 해당 변수가 true일 때 드롭박스가 표시될 수 있도록 해주었다.
const locationNow = useLocation();
if (locationNow.pathname.match(/\/(login|reset|find|auth|signup)/)) {
return null;
}
검색하면 다양한 방법들이 나오긴하는데, 해당 코드에서는 작동이 안 되는 게 많았고 그렇게 찾아낸 방법이 useLocation을 이용하는 것이다.
useLocation은 React Router에서 지원하는 hook으로 현재 URL 경로에 대한 정보를 제공해준다. pathname 속성을 이용해 URL 경로 값을 가져오고, 해당 값이 login, reset, find, auth, signup과 같을 경우에는 null값을 반환하여 header를 표시하지 않도록 한다.
일단 가장 중요하게 해결해야 하는 게 있다.
sidebar는 왼쪽에 존재하고, 오른쪽에는 URL에 맞는 페이지가 표시되어야 한다. sidebar를 모든 페이지에 추가해주는 건 코드가 너무 중복되기 때문에 좋은 방법이 아니다. 그렇다면, header와 같은 방법을 하면 될까? 이 방법은 틀린 방법이다. 이렇게 될 경우, 헤더-sidebar-페이지가 세로로 표시되게 된다. sidebar와 페이지는 같은 위치에 있어야 한다. 그렇다면 sidebar와 페이지를 container로 담아주면 되지 않을까! 이 방법을 이용해 구현하였다.
<BrowserRouter>
<Header />
<Center>
<Sidebar />
<Routes>
<Route index element={<Main />} />
*다른 페이지들은 생략*
</Routes>
</Center>
</BrowserRouter>
<Center>라는 컨테이너를 이용해 sidebar와 페이지를 담아주었다.
const Center = styled.div`
display: flex;
flex-direction: row;
`;
Center는 row 방향으로 요소를 담는 빈 div 요소이다.
const Wrapper = styled.div`
margin-left: 230px;
**그 외 속성은 생략**
/* 테블릿 가로, 테블릿 세로*/
@media all and (min-width: 768px) and (max-width: 1023px) {
margin-left: 170px;
**그 외 속성은 생략**
}
/* 모바일 가로, 모바일 세로*/
@media all and (max-width: 767px) {
margin-left: 0;
**그 외 속성은 생략**
}
`;
sidebar의 width만큼 페이지의 왼쪽은 비워두어야 한다. margin-left 속성을 이용해 sidebar의 width만큼 비워주었다.
const Side = styled.div`
position: fixed;
left: 0;
top: 0;
sidebar를 왼쪽에 고정해두기 위해 position: fixed를 이용해준다.
const menus = [
{ name: '음식점', path: '/list/restaurant' },
{ name: '카페', path: '/list/cafe' },
{ name: '놀거리', path: '/list/entertainment' },
];
<Menu>
{menus.map((menu, index) => {
return <SidebarItem menu={menu} />;
})}
</Menu>
menu는 menus 배열과 sideItem 컴포넌트를 만들어 map을 이용해 출력해주었다.
switch (menu.name) {
case '음식점':
icon = <MdOutlineRestaurant size="25" />;
break;
case '카페':
icon = <FaCoffee size="25" />;
break;
case '놀거리':
icon = <LuPopcorn size="25" />;
break;
default:
icon = null;
break;
}
switch문을 이용해 menu의 name에 따른 icon을 결정해준다. 각각의 컴포넌트는 모두 React Icons이다.
현재 위치한 메뉴는 사진처럼 아이콘과 글 색상을 파란색으로 표시해주었다. 현재 위치한 페이지를 알기 위해 React Router의 NavLink을 이용했다.
<Menu
to={menu.path}
style={({ isActive }) => {
return isActive ? activeStyle : deactiveStyle;
}}
>
{icon && <span style={{ marginRight: '10px' }}>{icon}</span>}
{menu.name}
</Menu>
Menu는 NavLink이고, 현재 위치한 URL과 이동할 URL이 같은 경우, style 속성을 사용해주었다.
const activeStyle = {
color: '#00a3e0',
};
const deactiveStyle = {
color: '#84828a',
};
각각의 속성은 다음과 같이 지정해주었다.
이렇게 정리해보니 참 쉬운 것처럼 보이지만... 이 과정 속에서 수많은 오류를 만났다... 정말...
header와 sidebar도 힘들었지만 앞으로 겪을 모든 것들이 다 힘들고 힘들었다. 앞으로도 힘들겠지..
현재 UI는 다 구현했고, firebase 연동을 하고 있다.. UI가 다 구현되니까 그래도 만족스럽게 잘 나와서 뿌듯하긴 하지만... 아직도 마음에 들지 않는 코드들이 많다. 좀 더 나은 방법이 있을텐데.. 생각이 들지만ㅜㅜ 아직은 두 번째 프로젝트니까..
UI는 아무래도 모든 걸 다 보여주기엔 애매해서 해당 정리는 짧게 끝내려고 한다. 꼭 필요한 내용만 정리하고 호다닥 firebase랑 React로 넘어가도록 하겠다.