프로젝트/하루한냥

[하루한냥] 레이아웃 구현 : 반응형 웹페이지를 위한 준비

알파카털파카 2023. 7. 1. 19:26
[하루한냥]
레이아웃 구현 : 반응형 웹페이지를 위한 준비

 

 

프로젝트를 모바일에 최적화된 화면으로 기획했기 때문에 다양한 모바일 기종의 화면 크기에 맞추는 작업이 필요하다. 레이아웃 구현 단계를 진행하며 반응형으로 구현하고자 신경 쓴 부분이 크게 3가지가 있었다. 첫째로는 Layout 컴포넌트의 스타일 속성, 둘째로 스타일 토큰에서의 사이즈 지정, 마지막으로 Body 컴포넌트에서의 flex 속성 적용이 있다. 이번 포스트에서는 반응형 프로젝트를 만들기 위한 코드를 기록하려 한다. 

 

 


 

 

1. Page, Layout 컴포넌트 구현

우선 배경이 될 Page 컴포넌트에서는 width, height를 100vw, 100vh로 잡고 연한 회색으로 칠해주었다. Layout 컴포넌트에서는 모바일 최적화를 위해 440x920으로 크기를 잡아주었다. 반응형 페이지를 구현하기 위해서는 Layout 컴포넌트가 핵심이 되는데, 스타일링 파트에서 다시 알아보도록 할 것이다.

 

Page, Layout 컴포넌트를 적용한 모습

 

// src/ui/components/layout/Page.tsx

import styled from '@emotion/styled';

import { PropsWithChildren } from 'react';

export default function Page({ children }: PropsWithChildren) {
  return <Container>{children}</Container>;
}

const Container = styled.div`
  width: 100vw;
  height: 100vh;
  background-color: #e9e9e9;
`;
// src/ui/components/layout/Layout.tsx

import styled from '@emotion/styled';

import { PropsWithChildren } from 'react';

export default function Layout({ children }: PropsWithChildren) {
  return <Container>{children}</Container>;
}

const Container = styled.div`
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 440px;
  height: 100%;
  max-height: 920px;
  background-color: #ffffff;
`;

 

 

 

1-1. Page, Layout 컴포넌트 적용

처음에는 Page 컴포넌트 안에 Layout 컴포넌트를 import해서 사용했다. 이 Page 컴포넌트를 App.tsx에서 호출했다.

 

export default function Page({ children }: Props) {
  <Container>
    <Layout>
      {children}
    </Layout>
  </Container>
}
export default function App() {
  return (
    <Page>
      <RouterProvider router={router} />
      <Global styles={globalStyle} />
    </Page>
  );
}

 

 

이번 프로젝트에서는 라우터 객체를 만들어 사용하고 있기 때문에, RouterLayout 컴포넌트를 활용해 기본적인 컴포넌트의 레이아웃을 잡아주었다. react router의 outlet을 사용하면 해당 자리에서 URL 주소에 따라 동적으로 컴포넌트가 렌더링된다. 이 라우터 레이아웃을 routes 객체의 element에 넣어주고, react router의 createBrowserRouterRouterProvider 이용해 App에서 렌더링하면 context API 방식으로 라우팅을 구현할 수 있다.

 

// src/ui/components/RouterLayout.tsx

import { Outlet } from 'react-router-dom';
import { Layout, Page } from '@ui/components/layout';

export default function RouterLayout() {
  return (
    <Page>
      <Layout>
        <Outlet />
      </Layout>
    </Page>
  );
}
// src/routes.tsx

const routes = [
  {
    element: <RouterLayout />,
    children: [
      { path: PATH.HOME, element: <HomePage /> },
      { path: PATH.SIGN_IN, element: <SigninPage /> },
      { path: PATH.SIGN_UP, element: <SignupPage /> },
      { path: PATH.CALENDAR, element: <CalendarPage /> },
      { path: PATH.TIMELINE, element: <TimelinePage /> },
      { path: PATH.REPORT, element: <ReportPage /> },
      { path: PATH.SETTING, element: <SettingPage /> },
    ],
  },
];
// src/App.tsx

import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { Global } from '@emotion/react';

import globalStyle from '@ui/styles/globalStyle.css';
import routes from './routes';

const router = createBrowserRouter(routes);

export default function App() {
  return (
    <>
      <RouterProvider router={router} />
      <Global styles={globalStyle} />
    </>
  );
}

 

 

 

1-2. Layout 컴포넌트 스타일링

레이아웃은 화면이 크고 작아짐에 따라 항상 화면의 정중앙에 위치해야 하기 때문에, position 관련 속성을 이용해 가운데로 정렬해 주었다. 또한 Layout 컴포넌트 안에서 렌더링될 children 요소의 스타일을 지정할 수 있다. 이를 위해 display: flex를 적용했다. 자식 요소는 헤더, 바디, 푸터가 세로 형태로 조합되기 때문에 flex-direction을 column으로 주었다. 만약 가로로 보여줄 페이지를 구현한다면 이 속성값을 row로 주면 된다. 

 

const Container = styled.div`
  // 화면의 정중앙에 위치하도록 정렬
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  
  // Layout 안에서 렌더링될 children 요소의 스타일
  display: flex;
  flex-direction: column;
  
  // Layout 컴포넌트의 크기 
  width: 100%;
  max-width: 440px;
  height: 100%;
  max-height: 920px;
`;

 

 

 

2. 스타일 토큰 사이즈 지정 

자주 사용하게 될 스타일 속성을 모은 styleToken 객체를 만들었다. 객체 안에는 크기와 색상을 지정해 두었다. 특히 사이즈 부분을 살펴보자면 header와 menu(네비게이션바, footer)의 크기는 80px로 고정했다. Body 컴포넌트의 크기는 전체 크기 내에서 유동적으로 조절된다. 

 

// src/ui/styles/styleToken

const headerHeight = 80;
const menuHeight = 80;
const SIZE_PROPERTIES = {
  headerHeight: `${headerHeight}px`,
  menuHeight: `${menuHeight}px`,
};

const styleToken = {
  size: SIZE_PROPERTIES,
  color: COLOR_PROPERTIES,
};

 

 

 

3. Body에 flex 속성 적용

바디 컴포넌트의 크기를 flex 속성을 이용해 반응형으로 만들어 보았다. flex item에 부여할 속성으로 flex: 1 0 auto; 를 주었다. 풀어서 쓰면 flex-grow: 1; flex-shrink: 0; flex-basis: auto; 와 같은 코드이다. flex-grow를 통해 내부 요소가 1의 비율로 늘어나게 된다. flex-shrink가 0이기 때문에 flex-basis의 크기 아래로는 줄어들지 않는다. 이렇게 flex 속성을 이용해 가변적으로 바디의 크기가 줄어들 수 있도록 구현했다. 

 

일반적으로 모바일 화면의 경우 세로로 보기 때문에 flex-direction을 column으로 주었다. Body 안에 들어오는 요소들을 세로로 정렬되도록 구현했다.

 

Body 컴포넌트를 표시한 스크린샷

 

// src/ui/components/layout/Body.tsx

import { PropsWithChildren } from 'react';
import styled from '@emotion/styled';
import styleToken from '../../styles/styleToken.css';

export default function Body({ children }: PropsWithChildren) {
  return <Container>{children}</Container>;
}

const Container = styled.div`
  height: 100px;
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
  background-color: ${styleToken.color.background};
`;

 

 

 

마치며

반응형으로 구현하기 위해 상대 단위를 사용하고, flex 속성을 통해 컨텐츠의 크기를 유동적으로 조정되도록 했다. 라우터 객체를 이용하는 방법은 얼마 전에 학습한 내용인데, 프로젝트에서 직접 사용해보니 이해가 더 잘 되었다. 다양한 모바일 기기 화면에 잘 맞춰지도록 여러가지 반응형 구현 방법에 대해 깊이 알아보는 기회가 되었다.