플렉스(flex) 박스, 한 번 정리하고 가자

화면을 개발할 때마다 곤혹스러운 점이 있는데 "내가 플렉스를 알고 사용하는건가?" 라는 생각이 들 때 그렇다. 스타일을 수정하면서 어찌 어찌 레이아웃을 만들어 내긴 하는데 이게 제대로 알고 하는게 아닌것 같아서 좀 찝찝하다.

플렉스 관련 글이 많이 있지만 (이 글이 좋다. A Complete Guide to Flexbox) 정리해 보면서 한 번더 손에 익혀야겠다.

컨테이너와 아이템

플렉스는 두 가지 요소로 이뤄져 있다.

플랙스 컨테이너라고 부르는 녀석은 여러 개의 플랙스 아이템을 포함한 부모 역할을 한다. 박스가 늘어나는 방향(flex-direction)과 박스가 다음 줄로 넘어가는 방식(flex-wrap)을 정한다. 그리고 자식요소인 플렉스 아이템을 정렬(align-item, justify-content)하는 것이 플렉스 컨테이너의 역할이다.

플렉스 아이템은 플렉스 컨테이너 안에 위치하면서 박스 하나 하나의 모양을 결정한다. 박스의 크기(flex-basis)와 그 크기가 변경되는 방식(flex-grow, flex-shrink)을 정의할 수 있다. 뿐만 아니라 플렉스 컨테이너 안에서 나열되는 플렉스 아이템 순서(order)도 정한다. 플렉스 컨테이너가 플렉스 아이템을 정렬하지만 플렉스 아이템 스스로 정렬 방식을 정할 수도 있다(align-self).

이러한 플랙스 박스 속성들을 어떻게 활용할 수 있는지 그리드 레이아웃을 만들어 보면서 익혀보자.

그리드 레이아웃 만들기

플랙스 박스를 알기 전에는 레이아웃 구성을 위해 float 속성을 주로 사용했다. 이것도 원리를 이해하고 사용했다기 보다는 꼼수처럼 보이는 여러 팁을 구글링해서 사용했다. 사용할때마다 예상했던 것과 다른 모습으로 결과물이 나오다보니 매번 삽질을 많이 하는 편이었다.

요즘에는 레이아웃을 만들 때 플렉스 박스를 많이 사용한다. 이전 보다는 비교적 예측한대로 박스가 배치되는 것 같다.

행과 열로 구성된 그리드 레이아웃은 트위터 부트스트랩을 포함한 여러 CSS 라이브러리에서 기본으로 제공하는 요소이다. 부트스트랩 코드를 참고해서 플랙스로 그리드 레이아웃을 어떻게 만들었는지 한 번 들여다 보자.

행(row)

먼저 .row 클래스로 플랙스 컨테이너를 정의한다. 박스모델을 정의하는 display 속성에 flex 값을 사용하면 플렉스 컨테이너를 만들 수 있다.

.row {
  display: flex;
}

플렉스 컨테이너는 안에 요소가 늘어나면 차곡차곡 가로로 채워지는데 한 줄로 쭉 늘여 세운다. 내가 원하는 것은 자식 요소가 다음 줄로 넘어가는 것인데 flex-wrap 속성이 이 역할을 한다.

.row {
  display: flex;
  flex-wrap: wrap;
}

플렉스 컨테이너 안에 있는 요소가 늘어가 컨테이너 너비를 넘어가면 다음 줄로 요소를 배치한다. 이렇게 해서 그리드 레이아웃의 행을 표현하는 .row 클래스를 만들었다.

열(column)

.row가 플렉스 컨테이너 였다면 .col은 자식 요소인 플렉스 아이템으로 만들어 볼 수 있다. 행이 열을 포함하듯 플렉스 컨테이너가 플렉스 아이템을 포함하니깐 중첩 구조가 딱 맞다.

플렉스 컨테이너의 자식요소는 자동으로 플렉스 아이템이 되는데 기본적으로 자식요소는 컨텐츠 크기만큼 공간을 할당한다. 열을 행에 꽉 차게 만들고 싶은데 flex-gorw 속성을 이용한다.

flex-grow는 플렉스 아이템이 플랙스 컨테이너 안에서 차지하는 크기의 정도를 나타내는데 기본값이 0이다. 아래처럼 1로 설정하면 컨테이너의 크기만큼 꽉찬 아이템을 그릴 수 있다.

.col {
  flex-basis: 0;
  flex-grow: 1;
}

col
col

아이템 크기를 지정할 수도 있어야 하는데 flex-basis 속성을 사용한다. 아이템의 초기 크기를 지정하는데 이것을 50% 값으로 설정하면 부모인 플랙스 컨테이너 너비의 절반 크기인 아이템이 된다.

.col-6 {
  flex-grow: 0;
  flex-shrink: 0;
  flex-basis: 50%;
}

열두개 단위의 크기를 사용하는 트위터 부트스트랩은 50% 컬럼 너비의 .col-6 클래스를 제공한다. flex-basis 아이템 크기를 지정했다면 늘어나는 정도를 정하는 flex-growflex-shrik 속성의 값을 0으로 주어 변동되지 않도록 고정해야한다.

col-6
col-6

아이템 크기와 관련된 이 세 개 속성은 하나의 축약 표현을 많이 사용하는데 flex 속성에 flex-grow, flex-shrink, flex-basis 값을 순서대로 지정한다.

.col-6 {
  flex: 0 0 50%;
}

정렬

요소의 수직 정렬을 맞추는게 좀 까다로웠다. 잘 외워지지 않고 매번 방법을 구글링해서 해결했는데 확신이 서질 않았다.

플렉스 박스를 사용한면 플렉스 컨테이너 안의 플레스 아이템을 수월하게 정렬할 수 있는데 align-itemsjustify-content 속성을 사용하면 그렇다.

이 두 속성을 제대로 사용하려면 플랙스 박스에 존재하는 두 개의 축을 이해하는 것이 먼저다.

  • 주축(Main Axis)
  • 교차축(Cross Axis)

이것부터 정리하고 정렬로 넘어가자.

주축과 교차축

주축은 플랙스 아이템이 쌓이는 방향인데 flex-direction 속성이 정한다. row일 경우 주축은 인라인 방향, 다시말해 글을 작성하는 방향이 된다. 한글이나 영어를 사용하는 브라우져의 경우 왼쪽에서 오른쪽 방향이다. 플렉스 아이템은 왼쪽부터 시작해서 오른쪽 방행으로 하나씩 쌓일 것이다.

이때 교차축은 주축을 가로지르는 방향이다. 블록 방향이다. 위에서 아래 방향이다.

flex-direction의 값에 따라 주축과 교차축의 방향이 변하는데 row-revers, column, column-revers 등의 값이있다. 여기서는 row로 설정해서 정렬해 보겠다.

수직 정렬

자식 요소를 수직 정렬하기 위해서는, 다시 말해서 위, 중앙, 하단 중 하나로 요소를 위치 시키기 위해서는 교차축을 기준으로 해야한다. align-items를 이용하면 교차축을 기준으로 정렬할 수 있다.

컨테이너가 정렬

flex-start로 설정하면 플랙스 컨테이너 상단에 아이템들을 정렬할 수 있는데 교차축 방향이 위에서 아래로 가기때문에 start가 위가 된다. 반대로 flex-end로 설정하면 교차축 방향인 하단으로 정렬된다. center는 수직선에서 중앙에 위치한다.

.align-items-start {
  align-items: flex-start !important;
}
.align-items-center {
  align-items: center !important;
}
.align-items-end {
  align-items: flex-end !important;
}

align-items
align-items

아이템이 스스로 정렬

플랙스 컨테이너에서 아이템들을 일괄적로 정렬하는 방법과 달리, 플렉스 아이템 스스로 교차축을 기준으로 정렬하는 방법인 있는데 그것이 바로 align-self 속성이다. 속성값의 역할은 align-items와 같다.

.align-self-start {
  align-self: flex-start !important;
}
.align-self-center {
  align-self: center !important;
}
.align-self-end {
  align-self: flex-end !important;
}

align-items
align-items

수평 정렬

한편 수평으로 정렬하려면 주축을 기준으로 해야한다. 이것도 여전히 flex-direction 속성이 row 값일 경우, 즉 주축이 인라인 방향일 경우에 해당한다.

justify-content 속성은 주축상의 요소를 정렬하는 기능이다. 주축으로 아이템이 배치되는데 그것을 flex-start로 하면 주축이 시작되는 방향인 왼쪽으로 정렬된다. 마찬가지로 flex-end는 주축이 끝나는 오른쪽, center는 중앙 정렬이다.

.justify-content-start {
  justify-content: flex-start !important;
}
.justify-content-start {
  justify-content: center !important;
}
.justify-content-end {
  justify-content: flex-end !important;
}

col
col

아이템 간의 간격을 부여해서 정렬해야 하는 경우도 있는데 두 가지 값을 사용한다. space-around로 설정하면 모든 아이템 양쪽에 같은 크기의 마진을 부여한다. space-between은 아이템 사이의 공간을 똑같은 크기로 표시한다.

.justify-content-around {
  justify-content: space-around !important;
}
.justify-content-between {
  justify-content: space-between !important;
}

justify-content-2
justify-content-2

그리드 전체 코드는 아래를 참고하자.

See the Pen Flex Grid Layout by Jeonghwan Kim (@JeonghwanKim) on CodePen.

[예시] 중앙 정렬된 모달 만들기

플렉스 박스의 주축과 교차축을 이용해 정렬하는 방법을 이용하면 요소를 화면 정중앙에 위치시키는 것이 무척 간단해진다. 좌우로 뻗은 주축상의 중앙을 맞추고 상하로 뻗은 교차축상의 중앙을 맞추면 그만이다.

.modal {
  display: flex;
  align-items: center;
  justify-content: center;
}

See the Pen Flex Modal by Jeonghwan Kim (@JeonghwanKim) on CodePen.

순서

플렉스 아이템은 문서의 흐름과 별개로 직접 요소의 순서를 지정할 수 있다. 플렉스 아이템의 order 속성이 바로 그것이다.

.order-1 {
  order: 1;
}
.order-12 {
  order: 12;
}

주축 기준으로 작은 숫자에서 큰숫자 순으로 정렬된다. 트위터 부트스트랩은 그리드처럼 정렬에도 열두가지 순서를 둬서 .order-1 부터 .order-12까지 클래스를 만들어 두었다.

.order-first {
  order: -1;
}
.order-last {
  order: 13;
}

예외로 맨 앞에 요소를 두기 위해 order: -1을 사용하거나 맨 뒤로 뒤기위해 order: 13을 설정한 유틸리티성 클래스도 제공한다.

[예시] 반응형 레이아웃

모바일과 데스크탑 화면에서 크기에 적절한 반응형 디자인에 order 속성을 사용하면 무척 단순하게 코드를 관리할 수 있다.

<div class="wrapper">
  <header class="header">Header</header>
  <main class="main">Main</main>
  <aside class="aside-1">Aside 1</aside>
  <aside class="aside-2">Aside 2</aside>
  <footer class="footer">Footer</footer>
</div>

Header, Main, Aside 1, Aside 2, Footer 순서대로 문서를 작성한다.

.wrapper {
  display: flex;
  flex-wrap: wrap;
}
.wrapper > * {
  flex-grow: 1;
  flex-basis: 100%;
}
.header {
  background-color: pink;
}
.main {
  height: 160px;
}
.aside-1 {
  background-color: orange;
}
.aside-2 {
  background-color: lightblue;
}
.footer {
  background-color: lime;
}

wrapper로 플랙스 컨테이너를 만들어 레이아웃 요소들을 플랙스 아이템으로 만든다. 플랙스 아이템에 flex-grow: 1, flex-basis: 100%을 부여해서 한 행을 꽉 채우도록 만들었다. 그럼 문서에서 정의한 순서대로 각 요소를 한 줄씩 그린다.

mobile
mobile

데스크탑일 경우 넓어진 가로 길이 만큼 요소를 적절하게 위치시키는게 좋은데 일명 "성화 레이아웃"으로 만들어 보겠다.

첫째 줄에 헤더를 두고, 둘째 줄에는 사이드1, 메인, 사이드2를 둔다. 그리고 마지막 줄에는 푸터를 두는 방식이다. 플랙스 아이템의 순서를 정하는 order 속성을 활용하자.

@media all and (min-width: 860px) {
  .header {
    order: 1;
  }
  .aside-1 {
    order: 2;
  }
  .main {
    order: 3;
  }
  .aside-2 {
    order: 4;
  }
  .footer {
    order: 5;
  }
}

문서의 순서와 상관없이 그려지는 순서를 order 속성으로 지정했다.

@media all and (min-width: 860px) {
  .main, .aside-1, .aside-2 { flex-grow: 1; flex-basis: 0; }
  .aside-1, .aside-2 { max-width: 240px;}

메인과 사이드 1,2를 한 줄에 두어야하는데 flex-grow: 1; flex-basis: 0;을 설정했다. 그리고 사이드 너비에 제한을 두어 화면 너비가 늘어나더라도 240픽셀로 제한하고 메인 그만큼 더 늘어나도록 크기를 맞추었다.

desktop
desktop

전체 코드는 아래를 확인하자.

See the Pen Flex Responsive Layout by Jeonghwan Kim (@JeonghwanKim) on CodePen.

정리

플랙스 박스는 여기저기 꽤 많이 사용할 수 있다. 여기서는 레이아웃과 팝업 예제만 사용했지만 버튼, 폼, 토스트 등 대부분 플랙스 박스로 해결했다.

단, 인터넷 익스플로러는 아직 플랙스 박스를 제대로 지원하고 있지 않기 때문에 확인하고 사용해야한다. 새롭게 버전업하는 트위터 부트스트랩 5 버전에서는 IE10과 모바일 IE11을 제거한다(#30363)고 하니 좀 더 지켜봐야겠다.