CSS 강의 2편. Flexbox Growing, Shrinking, Gaps, Wrapping 이해

안녕하세요?

CSS 강의 2편입니다.

지난 시간에 이어 Flexbox에 대해 심도있게 공부해 보겠습니다.

전체 강의 리스트입니다.

  1. CSS 강의 1편. Flexbox 완벽 이해

  2. CSS 강의 2편. Flexbox Growing, Shrinking, Gaps, Wrapping 이해

  3. CSS 강의 3편. Grid Layout 완벽 이해

  4. CSS 강의 4편. CSS Variables와 calc 함수 완벽 이해

  5. CSS 강의 5편. CSS에서 div를 중앙에 위치시키는 방법


** 목 차 **

  1. Hypothetical size

  2. Grwoing and Shrinking

    2.1 flex-basis

    2.2 flex-grow

    2.3 flex-shrink

    2.4 shrink 방지

    2.5 the minimum size

  3. Gaps

    3.1 Auto margins

  4. Wrapping


1. Hypothetical size

<style>
  .flex-wrapper {
    display: flex;
  }
  .item {
    width: 2000px;
  }
</style>

<div class="item"></div>

<div class="flex-wrapper">
  <div class="item"></div>
</div>

위와 같이 item 클래스에 width를 2000px이라고 아주 크게 설정했습니다.

그리고 flex가 해당 width를 어떻게 처리하는지 보기 위해 두 번째 div는 flex로 감샀습니다.

결과를 보시면 위와 같이 첫 번째 div는 아주 기다란 width를 가지는데, 두 번째 div 즉, flex로 감쌌던 div는 화면에 알맞게 적당한 width를 가지고 있네요.

왜 그런 걸까요?

바로 Hypothetical size(가설적 크기) 때문입니다.

1편에서도 언급했던 레이아웃 모드에 따라 작동 방식이 다르기 때문인데요.

첫번 째 div는 CSS의 가장 기본적인 레이아웃 모드인 Flow Layout에 따라 작동합니다.

Flow Layout에서는 width는 강제 제약 조건(hard constraint)입니다.

사용자가 2000px 이라고 지정하면 브라우저는 화면밖으로 튀어 나가든 말든 상관 안 하고 강제 제약 조건에 따라 무조건 2000px로 렌더링하게 됩니다.

그러면 두 번째 div인 flexbox에서는 어떻게 될까요?

레이아웃 모드가 Flexbox일 때는 width는 약간 다르게 구현됩니다.

강제 제약 조건이 아니라 상황에 맞게 Flexbox가 알아서 지정해 주는 그런 개념입니다.

Hypothetical size(가설적 크기)는 완벽한 유토피아에 살고 있다고 가정했을 때, 알아서 화면 크기에 따라 width를 계산해 준다고 생각하시면 됩니다.


2. Grwoing and Shrinking

Hypothetical size(가설적 크기)에 따라 flexbox 아주 유연하게 작동된다고 위에서 배웠는데요, 이제 실제 flex-grow, flex-shrink, flex-basis를 이용해서 어떻게 변하는지 살펴보겠습니다.

2-1. flex-basis

flex-basis를 쉽게 이해하려면 아래와 같이 생각하시면 편합니다.

fflex-direction이 row일 때는 flex-basis는 width 같이 작동하고 flex-direction이 column일 때는 flex-basis는 height처럼 작동한다고 생각하시면 됩니다.

좀 더 상세히 설명하자면, 일반적인 Flow 레이아웃 모드에서는 width와 height는 각각 width는 수평 사이즈에 영향을 주고, height는 수직 사이즈에 영향을 줍니다.

그런데 flexbox에서는 row와 column에 따라 수평, 수직이 아무렇지 않게 바뀌는데요.

그래서 flexbox 개념을 처음 개발할 때 row, column에 구애받지 않고, flexbox의 Primary Axis(주축)에만 영향을 받는 'size' 항목을 만들었는데요.

이 항목이 바로 'flex-basis'입니다.

flex-basis는 오직 primary axis에 적용되는 'size' 항목인데요, 그래서 flex-direction이 row, column일 경우 각각 width가 될 수도 있고, height가 될 수도 있는 겁니다.

그림을 보면서 이해하시면 좀 더 쉽습니다.

위 그림은 flex-basis가 50일 때입니다.

flex-basis를 좀 더 키워볼까요?

176일 때입니다.

flex-basis가 280일 때인데요.

flex-basis의 size도 강제 제약 조건에 따라 정해지는 게 아니라 Hypothetical size처럼 상황에 맞게 유연하게 사이즈가 책정됩니다.

다음은 flex column일 때입니다.

주축(primary axis)에 맞게 flex-basis가 작동되는 걸 보실 수 있습니다.


2-2. flex-grow

flexbox에서는 flex 컨텐츠가 가장 작은 알맞은 사이즈로 축소되는 게 디폴트 행동인데요.

이 경우 아래 그림에서 볼 수 있듯이 남는 공간이 생기게 됩니다.

이 경우 남는 공간을 쓸 수 있게 해주는 게 바로 flex-grow인데요.

두번 째 그림을 보시면 flex-direction이 row일 경우와 flex-grow가 1일 경우 남는 공간을 알맞게 다 쓰고 있습니다.

flex-direction이 column일 경우에도 마찬가지입니다.

flex-direction이 column 일 경우 위와 같이 작동합니다.

일반적으로, flex-grow 속성은 0 이상의 어떤 숫자든 가질 수 있는데요.

flex-grow 값은 해당 flex 아이템이 사용할 수 있는 남는 공간을 얼마나 차지할지를 결정합니다.

일반적으로 flex-grow 값이 0이면 남는 공간을 차지하지 않으며, 1이면 사용할 수 있는 모든 추가 공간을 차지합니다.

그러면 만약 flex-grow 값이 2라면 다른 아이템들의 flex-grow 값이 1인 경우에 비해 두 배의 추가 공간을 차지하게 된다는 뜻입니다.

즉, 하위 아이템들에게 각각 flex-grow를 0이 아닌 값으로 지정해 준다면 전체 숫자의 몇 분의 몇 개념으로 공간을 계산하게 됩니다.

그림을 보면서 설명해 보겠습니다.

위 그림은 flex-grow가 1일 세팅된 두 개의 하위 요소가 나란히 있다고 보는 겁니다.

flex-grow의 합계가 2가 되고 첫 번째 요소는 1/2, 두 번째 요소도 1/2가 되는 거죠.

위 그림에서는 첫 번째 요소의 flex-grow가 2일 경우이고, 두 번째 요소는 flex-grow가 1일 경우입니다.

그러면 전체 합계는 3이 되고, 첫 번째 요소는 2/3가 되고, 두 번째 요소는 1/3이 되는 겁니다.

위 그림에서는 첫 번째 요소는 flex-grow가 3이고, 두 번째 요소는 flex-grow가 1입니다.

그래서 첫 번째 요소는 3/4만큼 공간을 차지하고, 두 번째 요소는 1/4만큼 공간을 차지하게 되는 겁니다.


2-3. flex-shrink

두 개의 flexbox를 감싸고 있는 전체 컨테이너가 아래와 같이 있다고 합시다.

전체 컨테이너의 크기는 600px이고 flexbox인 첫 번째는 300px, 두 번째는 150px입니다.

이럴 경우 컨테이너의 크기가 더 크기 때문에 위 그림과 같이 표시될 겁니다.

그런데 만약, 컨테이너 크기가 더 작다면 어떻게 될까요?

flexbox는 hypothetical size에 따라 알맞게 축소가 됩니다.

그러면 flex-shrink를 이용해서 사용자가 직접 그 비율을 지정할 수 있는데요.

flex-grow처럼 flex-shrink에도 0 이상의 숫자가 오면 되고, 해당 숫자를 모두 더해 분모 값으로 하고 개별 숫자를 분자값으로 하는 ratio(비율)이 됩니다.

위 그림에서 볼 수 있듯이 퍼센티지로 보면 같은 비율로 줄어들었습니다.

이게 핵심인데요.

다른 예를 볼까요?

위 그림에서는 두 개의 flex item이 각각 flex-shrink 가 똑같이 1일 경우입니다.

flex-basis 가 두 개 합쳐 500px 이고, 컨테이너 사이즈가 400px 일 때 똑같이 20%씩 줄어들었네요.

위 그림에서는 첫 번째 아이템의 flex-shrink가 3입니다.

그러면 전체가 4이고 첫 번째는 3/4 비율이니까 75px, 두번 째는 1/4 비율이니까 25px 만 줄어들게 됩니다.

다시 한번 상기해야 할 게 있는데요.

flex-grow나 flex-shrink 값으로는 0 이상의 값 아무거나 올 수 있습니다.

그래서 각각 flex-shrink가 1000이라고 해도 결과값은 똑같을 겁니다.

왜냐하면 비율로 따지면 똑같이 1/2이기 때문이죠.

즉, 명심해야 할 거는 바로 flex-grow, flex-shrink는 비율(ratio)로 작동된다는 겁니다.

그리고, flex-grow와 flex-shrink는 아래의 관점에서 이해하시기를 바랍니다.

flex-grow: 남는 공간을 어떻게 분배할 것인가? flex-shrink: 공간을 어떻게
지워버릴까?

여기서 중요한 점이 하나 더 있는데요, flex-grow나 flex-shrink는 둘 중에 하나만 작동된다는 점입니다.

쉽게 생각해 보면 남는 공간이 있을 때는 줄일 필요가 없기 때문에 flex-shrink는 작동하지 않을 겁니다.

반대로, 컨테이너보다 자식 요소가 훨씬 더 크다면 flex-grow가 작동하지 않을 겁니다.

왜냐하면 비율에 따라 나눌 공간이 없어서이지요.


2-4. shrink 방지

우리가 SVG 아이콘을 flex 컨테이너에 넣을 수도 있는데요.

이럴 때 만약 flex-shrink에 의해 SVG 아이콘까지 줄어든다면 영 보기가 좋지 않겠죠?

위 그림과 같이 아이콘이 들어갈 자리인 동그라미가 같이 줄어든 게 보일 겁니다.

이걸 방지하는 방법이 있는데요.

바로 해당 아이템의 flex-shrink를 0으로 지정하면 됩니다.

flex-shrink를 0으로 설정하면 본질적으로 shrink(축소) 프로세스에서 완전히 "견딤"으로 처리됩니다.

Flexbox 알고리즘은 flex-basis (또는 width)를 강제 최소 제한으로 여겨서 줄어드는 걸 방지할겁니다.


flex-shrink보다 좀 더 간단한 방법은?

간단합니다.

.item.icon {
  min-width: 32px;
}

바로 min-width 항목을 지정하는 겁니다.

min-width는 강제 제약 조건이 돼서 아이콘이 이 크기 밑으로는 절대 줄어들지 않을 겁니다.

그렇다고 해서 flex-shrink를 0으로 지정하는 방법이 더 복잡한 건 아니고, flexbox와 flex-shrink를 같이 쓰는게 좀 더 일관성이 있다고 볼 수 있습니다.


2-5. the minimum size

아래와 같은 input 창이 있다고 합시다.

만약 위 그림에서 전체 컨테이너가 아래처럼 줄어든다면 아래와 같은 현상이 발생되는데요.

전문적인 용어로, input 태그에 overflow가 발생되었다고 합니다.

왜냐하면 flex-shrink는 디폴트 값이 0이기 때문인데요.

"만약 input 태그아이템에 flex-shrink를 0으로 지정하면 되는거 아닌가요?" 라고 생각할 수 있습니다.

그런데, 여기서 hypothetical size와 같이 생각해야 할 게 있는데요.

바로 the minimum size 입니다.

Flexbox는 자식 요소를 해당 자식 요소의 the minimum size 이하로 shrink하는걸 거절합니다.

그래서 overflow가 발생하는 거죠.

input 태그는 기본적으로 170px~200px(브라우저에 따라 다르지만))의 the minimum size를 가집니다.

그래서 flexbox가 the minimum size 이하로 shrink가 되는 걸 거절하는 거죠.

input 태그 말고 다른 예를 들어 볼까요?

위와 같이 두 개의 p 태그가 있다고 합시다.

만약 전체 컨테이너를 아래와 같이 줄인다면 어떻게 될까요?

위와 같이 overflow가 발생될 겁니다.

왜냐하면 p 태그의 the minimum size는 바로 단어 중 끊을 수 없는 가장 긴 단어가 바로 the minimum size가 되는 거라서 그렇습니다.

그러면, 이 같은 문제를 어떻게 해결할까요?

바로 the minimum size를 다시 세팅하는 겁니다.

즉, min-width를 0px로 세팅하면 됩니다.

min-width를 0px로 세팅한다는 뜻은 flexbox 알고리즘에게 해당 요소의 the minimum size를 새로운 값으로 강제로 덮어씌운다는 뜻이 되는 겁니다.

그래서 아래와 같이 작동할 겁니다.

어떤가요?

참고로, flex-direction이 column일 경우 min-height를 0px로 덮어씌우면 됩니다.


min-width 사용 시 주의 사항

min-width 마법이 모두 작동하는 게 아닙니다.

아래 예를 보시면 p 태그에서는 제대로 작동되지 않습니다.

그러니까 min-width를 만능 해결법이라고 남발하지 마시기를 바랍니다.


3. Gaps

Flexbox에 추가된 항목 중에 가장 유용한 게 바로 'gap'인데요.

gap 항목의 작동 원리는 아래 그림과 같습니다.

gap 항목을 가장 잘 사용하는 방법은 바로 네비게이션 목록인데요.

위 세 개의 그림을 보시면 네비게이션 목록 만들기가 flexbox로 쉽게 구현되는 걸 볼 수 있을 겁니다.

여기서 gap 항목은 각 항목 사이즈 갭을 지정하는 역할을 하기 때문에 쉽게 멋진 UI를 만들 수 있을 겁니다.


3-1. Auto margins

Flow 레이아웃 모드에서 margin: auto라는 걸 이용해서 항목을 센터에 위치시키는 트릭을 구현했는데요.

Auto margins는 flexbox에서도 매직을 부립니다.

일단 아래와 같은 그림을 볼 때 margin-left와 margin-right를 디폴트 값으로 0을 지정한 겁니다.

아예 지정 안해도 디폴트 값이 되는 거죠.

이에 margin-left만 auto라고 지정해 볼까요?

즉, 노란색 아이템의 왼쪽 마진을 자동으로 지정하는 건데요.

flexbox의 알고리즘은 남는 공간의 사용과 관련되어 있습니다.

그래서 왼쪽 마진을 auto라고 지정하면 아래 그림과 같이 됩니다.

이게 왜 그렇냐 하면 flex-grow를 지정하면 flexbox 알고리즘은 남는 공간을 전부 차지해 버리죠.

margin-left: auto도 마찬가지입니다.

왼쪽 마진을 위해 남는 공간을 모두 차지해 버린 겁니다.

그러면 margin-right를 auto라고 추가로 지정해 볼까요?

어떤가요?

flexbox 알고리즘이 왼쪽, 오른쪽 마진을 auto라고 인식하고 합리적으로 50%씩의 비율로 주고 있습니다.

그래서 아이템이 센터에 놓이게 됩니다.

이같은 방식의 트릭을 가장 많이 쓰는 곳이 바로 네비게이션 목록에서 로고와 그다음 목록을 띄어서 놓을 경우인데요.

아래 그림과 같이 있다고 합시다.

logo와 a 태그에 대해 margin-right를 0으로 세팅한 결과 로고와 그다음 목록의 사이가 똑같습니다.

이걸 auto margins으로 해결해 볼까요?

당연히 flex-grow를 이용해서 해결할 수도 있지만 auto margins 방법이 훨씬 간단합니다.

위 그림과 같이 margin-right: auto라고 지정했습니다.

그러면 flexbox 알고리즘이 사용 가능한 오른쪽 남는 공간을 모두 차지하게 됩니다.

그래서 위 그림과같이 아주 깔끔한 UI가 나오는 거죠.

이 트릭을 꼭 외워두시기를 바랍니다.

많이 쓰이는 트릭이거든요.


4. Wrapping

지금까지 flexbox의 거의 모든 걸 공부했는데요.

마지막으로 가장 중요한 wrapping이 남았습니다.

flexbox에서는 아이템들이 각각 붙어서 가로축이나 세로축으로 늘어졌는데요.

flex-wrap: wrap 이란 걸 이용하면 이걸 변경할 수 있습니다.

디폴트 값은 flex-wrap: nowrap입니다.

위 세 개의 그림을 보시면 알 수 있듯이 flexbox의 flex-wrap 항목을 이용하면 CSS Grid에서 사용 가능한 레이아웃이 가능합니다.

그럼 여기서 더 생각해 봐야 할 게 있는데요.

wrap 이 되면 아이템이 밑으로 이동했습니다.

즉, row가 한 개 더 생겼다는 건데요.

그렇다면 primary axis와 cross axis 같은 건 어떻게 되는 걸까요?

내부적으로 flexbox는 wrap이 발생해서 아이템이 밑으로 밀리게 되면 밑으로 밀린 아이템에게도 mini flex container 방식을 적용합니다.

즉, 아래로 밀린 아이템들에게도 똑같이 primary axis와 cross axis가 적용됩니다.

그래서 justify-content와 align-items가 똑같이 작동되는 걸 볼 수 있을 겁니다.

align-items가 작동되는 걸 그림으로 구현해 볼까요?

위와 같이 align-items가 우리 예상대로 제대로 작동하고 있습니다.

wrap된 상태후라도 각 row가 mini flexbox 환경이 되기 때문에 align-items가 원래 의도대로 잘 작동되는 겁니다.

그러면 wrap 상태에서 row 자체를 align 하고 싶으면 어떻게 해야 할까요?

이럴 때를 위해 나온 항목이 바로 align-content입니다.

여러 가지 경우의 수를 그림으로 보면서 이해하시면 쉽습니다.

justify-content와 같이 content 관련이기 때문에 들어가는 변수가 똑같습니다.

요약하면 다음과 같습니다:

  • flex-wrap: wrap은 우리에게 두 줄의 "stuff"을 제공합니다.

  • 각 행 내에서 align-items를 사용하여 각 개별 자식을 위로 또는 아래로 이동시킬 수 있습니다.

  • 그러나 전체적으로 보면 이 두 행은 하나의 Flex 컨텐츠 내에 있습니다! 교차 축은 이제 하나가 아니라 두 개의 행을 교차합니다. 따라서 행을 개별적으로 이동시킬 수 없으며, 그룹으로 분배해야 합니다.

  • 위에서 정의한 대로 우리는 항목이 아닌 컨텐츠를 다루고 있습니다. 그러나 여전히 교차 축에 대해 이야기하고 있습니다! 그래서 align-content 항목을 이용해야 하는 겁니다.


지금까지 두 편에 걸쳐서 Flexbox에 대해 알아봤는데요.

Flexbox를 꼭 숙지해서 사용하시기 바랍니다.

정말 유연한 레이아웃을 구현할 수 있거든요.

그리고 아래 예제를 꼭 테스트해 보시길 바랍니다.

Flexbox의 에센스를 연구할 수 있을 겁니다.

<style>
  form {
    display: flex;
    align-items: flex-end;
    flex-wrap: wrap;
    gap: 8px;
  }
  .name {
    flex-grow: 1;
    flex-basis: 120px;
  }
  .email {
    flex-grow: 3;
    flex-basis: 170px;
  }
  button {
    flex-grow: 1;
    flex-basis: 70px;
  }
</style>

<form>
  <label class="name" for="name-field">
    Name:
    <input id="name-field" />
  </label>
  <label class="email" for="email-field">
    Email:
    <input id="email-field" type="email" />
  </label>
  <button>Submit</button>
</form>

끝.