Script/CSS

[SCSS, React, FSD아키텍쳐] Grid를 이용한 레이아웃 잡기

기록하는 습관. 2025. 1. 26. 21:43

들어가기 앞서서

프론트엔드 개발을 준비한지 대략 2주의 시간이 지났습니다. 준비를 하는 동안 새롭게 학습한 것들이 많은데 학습 곡선이 두 번째로 깊었던 건 CSS였습니다.


누군가는 다른 기술을 학습하는 것보다 CSS나 HTML이 쉽다고 하던데 나는 왜이리 학습 곡선이 깊었을까?

스스로에게 위의 질문을 하고 생각해보았습니다. 저는 개발에 있어서 일관성 있는 프로젝트의 구조와 코드의 재사용성을 가장 중요하게 생각합니다. CSS도 일관성있는 구조 및 의미있는 네이밍, 재사용성을 높이자는 열망을 갖고 임했습니다. 학습 초기에 이러한 생각이 저의 발목을 잡았고, 더뎌지지 않았나 싶습니다. 여러 시도 끝에 목표로하는 레이아웃을 만들게 되었고 이번 포스팅에 소개하고자 합니다.



STEP 1. CSS, Grid 레이아웃 잡기

해당 프로젝트는 React(TSX 기반)SCSSBEM 네이밍방식을 사용했습니다. 따라서 해당 개념들에 대한 이해가 있어야 포스팅을 이해하는데 도움이 됩니다.


STEP 1-1. 목표

[목표 상세]
 서비스 전반에서 사용될 레이아웃 골격 잡기

Image

  1. 왼쪽 패널 : 로고와 사이드바를 가지는 UI를 만든다. 왼쪽 패널은 스크롤이 되더라도 화면에 고정된다.
  2. 가운데 패널 : 메인 컨텐츠를 가지는 UI를 만든다.
  3. 오른쪽 패널 : 부수 컨텐츠를 가지는 UI를 만든다.

목표로 하는 레이아웃의 골격은 정말 단순합니다. 실제로 서비스 중인 디스콰이엇의 홈페이지를 벤치마킹했습니다. 해당 레이아웃의 왼쪽 패널, 가운데 패널, 오른쪽 패널의 골격은 대부분의 페이지에서 사용될 예정이고, 제가 중요하게 생각하는 일관된 프로젝트 구조 및 코드 재사용성, 의미있는 네이밍을 성취하기 위해 노력했음을 말씀드리며 앞으로 개발 방향과 여러 방법론 적용에 따라 CSS 세부 속성 값 또는 컴포넌트 내용이 바뀔 여지가 있음을 참고해주시기 바랍니다.



STEP 1-2. 구현 결과

Image

기본 레이아웃


Image

기본 레이아웃 스크롤 사용 (왼쪽 패널) 고정


Image

반응형 레이아웃 (1/2)

  • width 범위 : 931px ~ 1023px
  • 특징 : 오른쪽 패널 숨김(remove)


Image

반응형 레이아웃 (2/2)

  • width 범위 : 930px 이하
  • 특징 : 사이드바, 오른쪽 패널 숨김 (remove)


코드 설명에 앞서 구현 결과를 먼저 공개합니다. 반응형 레이아웃은 모든 디바이스에서 이질감 없이 화면을 출력하기 위해 작성해주었습니다. 다만, 현재 개발중인 페이지를 기준으로 모바일에서 화면을 어떻게 보여줄지 디자인을 생각해보지 않아 반응형 레이아웃 (2/2)의 임시 레이아웃 형태로 놔두었습니다.



STEP 1-3. React 폴더 구조와 핵심 파일만 알아보기

React와 관련해 언급을 하지 않으려고 했습니다. 하지만, 언급을 안 하고 설명하기는 어렵다고 판단하여 폴더구조와 파일들에 대해서만 간략히 기술합니다.


STEP 1-3-1. 폴더구조

Image

FSD 아키텍쳐를 적용했습니다. widgets은 페이지에서 독립적으로 사용되는 UI 컴포넌트들의 모음입니다. 제가 구현하고자하는 레이아웃은 전역적으로 사용될 여지가 있기 때문에 widets/GlobalLayout으로 관리하고 있습니다. ( GlobalSidebar도 같은 이유로 widgets에 위치합니다. )


STEP 1-3-2. GlobalLayout.tsx

Image

두 번째로 중요하다고 볼 수 있는GlobalLayout.tsx파일의 코드는 사진과 같습니다. 2번째 라인에서 styles.module.scss을 import하고 있습니다. 여기서 레이아웃과 관련한 스타일링을 응집도 높게 관리하여 재사용성을 높일려는 시도를 했으며 핵심파일이라고 볼 수 있습니다.


해당 코드를 보고 어색하게 느끼거나 의아하다고 생각하시는 분이 있으실거라 생각합니다. 제가 React를 학습한지 얼마되지 않아 동적으로 CSS를 처리하거나 파라메터로 받는 방식이 있는 것으로 알고 있는데 이는 아직 학습하기 이전이라 적용하지 못했습니다. 관련해서 조언주실 수 있는 부분이 있다면 적극적으로 알려주시면 너무 감사하겠습니다. :)


STEP 1-4. 레이아웃 스타일링 코드 알아보기

STEP 1-4-1. 전체코드

  • widgets/GlobalLayout/ui/styles.moudule.scss
.layout {
  display: grid;
  grid-template-areas:
    "center_panel  right_panel";
  grid-template-columns: 1fr 0.35fr;
  column-gap: 50px;
  box-sizing: border-box;
  margin-left: 20%;
}

.left_panel_container {
  position: fixed; // 스크롤 시 화면에 고정
  display: flex;
  height: 100vh;
  width: 172px; // 고정된 너비
  flex-direction: column;
  background-color: yellow;
  padding: 10px;
  box-sizing: border-box;
  gap: 100px;
  &__logo {
    background-color: #f4f4f4;
    padding: 16px;
    text-align: left;
    height: auto;
  }

  &__sidebar {
    display: flex; // Flexbox 사용
    align-items: center; // 수직 가운데 정렬
    justify-content: flex-start; // 수평 왼쪽 정렬
    border-radius: 8px;
    background-color: aqua;
    height: 100%;
  }
}

.center_panel_container {
  grid-area: center_panel;
  display: flex;
  flex-direction: column;
  background-color: rgb(74, 8, 77);
  padding: 10px;
  box-sizing: border-box;
  border-radius: 8px;
  gap: 30px;

  &__dashboard_top {
    grid-area: center_panel_top;
    background-color: blue;
    padding: 16px;
    border-radius: 8px;
    height: 65px;
  }

  &__dashboard_main {
    grid-area: center_panel_bottom;
    background-color: pink;
    padding: 16px;
    border-radius: 8px;
  }
}

.right_panel_container {
  grid-area: right_panel;
  background-color: green;
  border-radius: 8px;
}

@media (max-width: 1023px) {
  .layout {
    grid-template-columns: 1fr;
    grid-template-areas:
      "center_panel";
    margin-left: 20%;
  }

  .center_panel_container {
    gap: 20px;
  }

  .right_panel_container {
    display: none;
  }
}

@media (max-width: 930px) {
  .layout {
    grid-template-areas:
      "center_panel";
    column-gap: 10px;
    margin: 0.5%;
    justify-content: center; /* 가로 방향 중앙 정렬 */
    align-items: center; /* 세로 방향 중앙 정렬 */

  }

  .left_panel_container {
    display: none; // 작은 화면에서는 숨김
  }

  .center_panel_container {
    gap: 10px;
  }
}

설명에 앞서 작성된 전체 코드먼저 보여드립니다.



STEP 1-4-1. 상세 설명

조금 번거로우시더라도 GlobalLayout.tsx파일의 어디에 적용되었는지 확인해보시면 좋을 것 같습니다.

1.

.layout {
  display: grid;
  grid-template-areas:
    "center_panel  right_panel";
  grid-template-columns: 1fr 0.35fr; // grid column cell rate : (75%, 25%)
  column-gap: 50px; // grid column cell gap : 50px
  box-sizing: border-box;
  margin-left: 20%;
}

Grid를 적용하기 위한 전체 레이아웃을 정의하는 부분이라고 말씀드릴 수 있습니다. 특이한점은 left_panel은 그리드로 잡지 않은 것을 볼 수 있습니다. 작성 초기에는 left_panel을 그리드에 넣고 postition의 값을 fixed로 주어 작성을 했었는데요. 여백 공간의 스타일링이 매우 어렵다는걸 깨닿고 따로 작성해주었습니다.

정리하면 아래와 같습니다.

  • grid-template-areas
    • 그리드에서는 center_panel, right_panel만 잡아줌.
  • grid-template-columns: 1fr 0.35fr;
    • 각 레이어 크기는 화면 비율대비 75%, 25%를 두길 원함
  • box-sizing: border-box;
    • 컨텐츠의 크기가 pading이나 border를 포함한 크기이길 원함
  • margin-left: 20%;
    • 왼쪽의 margin을 20% 둠으로서, left_panel(사이드바)가 위치할 공간 만큼 띄어줌



2.

.left_panel_container {
  position: fixed; // 스크롤 시 화면에 고정
  display: flex;
  height: 100vh;
  width: 172px; // 고정된 너비
  flex-direction: column;
  background-color: yellow;
  padding: 10px;
  box-sizing: border-box;
  gap: 100px;
  &__logo {
    background-color: #f4f4f4;
    padding: 16px;
    text-align: left;
    height: auto;
  }

  &__sidebar {
    display: flex; // Flexbox 사용
    align-items: center; // 수직 가운데 정렬
    justify-content: flex-start; // 수평 왼쪽 정렬
    border-radius: 8px;
    background-color: aqua;
    height: 100%;
  }
}

앞서 말씀드렸던 것처럼 스크롤시에도 화면에 고정시키기 위해 postion을 fixed로 설정했습니다.
더불어 내부에 들어 오는 컨테이너의 항목은 flex로 관리하기 위해 display를 flex로 설정했으며 각 항목의 flex-direction은 column으로 주어 열로 나열되게 했습니다. 자식요소로 __logo__sidebar가 존재하는데 이는 left_panel_container의 자식 컨테이너를 갖게 하도록 하는 조치였습니다.


__sidebar내부에서도 flex를 활용하여 align-items: center;, justify-content: flex-start;를 적용해 각각 수직/수평 가운데 정렬이 될 수 있도록 조치했습니다. height: 100%의 필요성은 현재 점유하고 있는 부모 공간의 높이를 모두 차지하기 위해 넣어주었습니다.


해당 부분은 가장 고민이 많이 되는 부분입니다. 현재는 GlobalLayout 스타일에서 관리하기 보다는 GlobalSidebar 컴포넌트를 따로 생성하여 관리하는 것이 바람직할 것으로 생각하고 있습니다.



3.

.center_panel_container {
  grid-area: center_panel;
  display: flex;
  flex-direction: column;
  background-color: rgb(74, 8, 77);
  padding: 10px;
  box-sizing: border-box;
  border-radius: 8px;
  gap: 30px;

  &__dashboard_top {
    grid-area: center_panel_top;
    background-color: blue;
    padding: 16px;
    border-radius: 8px;
    height: 65px;
  }

  &__dashboard_main {
    grid-area: center_panel_bottom;
    background-color: pink;
    padding: 16px;
    border-radius: 8px;
  }
}

Grid영역의 center_panel의 공간을 차지하는 center_panel_container입니다. 해당부분은 left_panel_container와 비슷한 코드 구조를 가지고 있고, 컨테이너의 자식인 __dashboard_top, __dashboard_main의 gap차이를 30px를 두고 있는 구조를 가집니다.



4.

.right_panel_container {
  grid-area: right_panel;
  background-color: green;
  border-radius: 8px;
}

Grid영역의 right_panel의 공간을 차지하는 right_panel_container입니다. 해당 영역에는 어떤 정보들이 들어갈지 고민중에 있으며, 간단하게 구성만하고 넘어갔습니다.



5.

@media (max-width: 1023px) {
  .layout {
    grid-template-columns: 1fr;
    grid-template-areas:
      "center_panel";
    margin-left: 20%;
  }

  .center_panel_container {
    gap: 20px;
  }

  .right_panel_container {
    display: none;
  }
}

@media (max-width: 930px) {
  .layout {
    grid-template-areas:
      "center_panel";
    column-gap: 10px;
    margin: 0.5%;
    justify-content: center; /* 가로 방향 중앙 정렬 */
    align-items: center; /* 세로 방향 중앙 정렬 */

  }

  .left_panel_container {
    display: none; // 작은 화면에서는 숨김
  }

  .center_panel_container {
    gap: 10px;
  }

  .right_panel_container {
    display: none;
  }
}

반응형 UI를 위해 @media를 사용했습니다. 반응형마다의 Layout을 새로 설정해주었습니다.


  1. 가로가 930px ~ 1023px에 해당하는 부분은 @media (max-width: 1023px)에 해당됩니다. 해당 부분에서 특이사항으로는 .right_panel_container를 담는 정보를 display: none으로 두어 웹페이지에서 숨겼습니다. (공간차지 x)
  2. 가로가 930px 이하인 부분은 @media (max-width: 930px)에 해당됩니다. 특별한 코드는 따로 없으며 left_panel)containerright_panel_container를 숨김처리해주었습니다. 사실 right_panel_container은 display_none처리를 하지 않아도 되는데 명시적으로 한 번 더 작성해주었습니다.


⭐회고

아직 정제되지 않은 코드라 많은 부분에서 수정이 이루어질 것 같습니다. (왼쪽패널, 동적CSS 적용 등)
하지만 틀은 잡아둔 상태이기 때문에 가장 어렵다고 느낀 한 고비를 넘기게 되어 행복합니다.
마지막으로... 웹 퍼블리셔, 프론트엔드 개발자님들 정말 존경스럽습니다.