CSS Flex 아이템 크기 결정 알고리즘: 예시 편

이 글은 CSS Flex 아이템 크기 결정 알고리즘: 이론 편을 바탕으로 CSS Flex 아이템의 크기가 어떻게 결정되는지 구체적인 사례를 통해 이해해보는 글이다.

 

 

 

1. 고정 크기의 헤더, 푸터와 유동적인 본문

위 사진과 같이 헤더와 푸터의 높이는 고정이고, 본문의 크기는 viewport에 따라서 달라지는 레이아웃을 생각해보자. 이 레이아웃은 Flex를 사용하지 않고 CSS `calc()` 함수를 이용해서 만들 수도 있지만 요구사항이 바뀌어 헤더, 푸터의 높이가 달라진다면 각 요소의 높이를 개별적으로 수정해줘야 하는 불편함이 있다. Flex 레이아웃을 사용한다면 헤더, 푸터의 높이가 달라지더라도 본문의 높이는 수정해줄 필요가 없어 편하다. 이 레이아웃에서 CSS 코드는 아래와 같다.

.container {
  display: flex;
  flex-direction: column;
  height: 80vh;
  padding: 10px;
}

.header, .footer {
  flex: 0 0 60px;
}

.contents {
  flex: 1 1 0;
}

 

1. 플렉스 아이템들이 차지할 수 있는 공간(Container Inner Main Size)

`.container`의 height 80vh(800px이라고 해보자)에서 상하의 padding을 제외한 780px이 플렉스 아이템들이 차지할 수 있는 공간의 크기이다.

 

2. Flex Base Size

플렉스 아이템인 `.header`, `.footer` 요소에 `flex-basis`가 설정되어 있으므로 60px, `.contents` 요소는 0px이다.

 

3. Hypothetical Main Size

플렉스 아이템에 `min-width`, `max-width`가 설정되어 있지 않으므로 Flex Base Size에 적용할 제약 조건이 없다. 따라서 Flex Base Size를 기반으로 결정되지만, 현재 예시에서는 헤더, 푸터, 본문에 텍스트가 존재해 요소가 차지해야 하는 최소 크기가 존재하므로  Flex Base Size와 같다고는 할 수 없다. 정확한 크기는 4. Automatic Minimum Size에서 결정된다.

 

4. Automatic Minimum Size

`.header`, `.footer`, `.contents` 요소 모두 `width`가 없으므로 Specified Size Suggestion는 `undefined`, Content Size Suggestion는 `min-content`이다. 따라서 Automatic Minimum Size는 둘의 최솟값인 `min-content`(17.3px)이다. 한편,  `.header`, `.footer`에 설정된 `flex-basis` 크기가 더 크기 때문에 Hypothetical Main Size는 60px로 결정되고, `.contents`Automatic Minimum Size가 더 크기 때문에  Hypothetical Main Size는 17.3px로 결정된다.

Automatic Minimum Size은 해당 요소에 `flex: 0 0 auto`, `padding: 0`이 설정되었을 때의 크기라고 생각할 수 있다.

 

5. Resolve Flexible Lengths

우선 각 아이템들의 Outer Hypothetical Main Size를 구해보자. `.header`, `.footer`, `.contents` 모두 padding, margin이 없기 때문에 17.3px이다.

 

Container Inner Main Size는 780px인데 세 아이템들의 Outer Hypothetical Main Size 합은 51.9px로 이 값보다 작으므로 각 아이템들의 flex grow factor를 사용한다. `.contents`만 flex grow factor가 1이고, 나머지는 0이기 때문에 unfrozen item은 `.contents`뿐이다.

 

Initial Free Space는 780px에서 세 요소의 outer size(`.header`, `.footer`는 outer hypothetical size인 60px, `.contents`는 outer flex base size인 0px)의 합 120px을 뺀 660px이다. Remaining Free Space는 Initial Free Space와 동일한 660px인데 이 값을 `.contents`가 전부 가져가게 된다. `.contents`의 outer size는 Outer Flex Base Size 0pxRemaining Free Space 660px을 합한 660px이 된다.

 

6. Fix min/max Violations

unfrozen 아이템인 `.contents`에 대해 결정된 outer size는 used min size보다 크므로 이 단계에서 수정될 사항은 없다. 

 

7. Freeze over-flexed items

6에서 변동 사항이 없으므로 Total Violation = 0이다. 따라서 모든 아이템을 freeze하고 종료한다.

 

 


반응형 페이지에서 `.container`의 높이가 달라지거나, `.header`, `.footer`의 높이가 달라지더라도 `.header`, `.footer`가 `.container` 크기를 초과하지 않는 이상 Free Space를 `.contents`가 모두 가져가게 되므로 `.contents`는 헤더와 푸터를 제외한 나머지 영역을 모두 채울 수 있게 된다.      

 

 

 

2. padding이 존재하는 플렉스 아이템과 균등 너비

플렉스 아이템의 특정 요소에만 padding이 존재하는 경우 `flex-grow`, `flex-shrink`이 어떻게 설정되어 있는지에 따라 플렉스 아이템들의 크기가 달라지게 되는 `flex-basis` 경계값이 존재한다. 위 예시처럼 세 플렉스 아이템이 있을 때 한 요소에만 padding이 설정되어 있다면 세 요소의 크기가 달라지게 되는 경계값은 아래와 같다.   

flex-grow: 1, flex-shrink: 0일 때
flex-basis < 10%일 때: 세 요소의 크기가 달라짐

flex-grow: 0, flex-shrink: 1일 때
flex-basis < 10% 또는 flex-basis > 33.33...%일 때: 세 요소의 크기가 달라짐

왜 각 요소의 크기가 달라지는지 개별 수치에 대해 플렉스 아이템의 너비 결정 알고리즘을 적용해보면서 이해해보도록 하자. 

.item {
  flex: 1 0 10%;
  box-sizing: border-box;
}

.two {
  padding: 20px;
}

가운데 요소 `.two`에만 padding이 존재한다.

 

 

A. flex: 1 0 N%일 때

1. 플렉스 아이템들이 차지할 수 있는 공간(Container Inner Main Size)

400px

 

2. Flex Base Size

`box-sizing: border-box`이므로 N%는 inner flex base size + padding + margin이 된다. 따라서, 첫번째와 세번째 요소의 Flex Base Size는 N%, 두번째 요소는 양쪽의 padding을 제외한 N% - 40px이다.

 

3. Hypothetical Main Size

플렉스 아이템에 min-width, max-width가 설정되어 있지 않으므로 Flex Base Size에 적용할 제약 조건이 없다. 다만, 두번째 요소에는 padding이 존재하므로 두번째 요소의 크기는 40px보다 작을 수 없다. 즉, min(N%, 40px).

 

4. Automatic Minimum Size

세 요소 모두 width가 없으므로 Specified Size Suggestion는 `undefined`, Content Size Suggestion는 `min-content`이지만 내부 요소가 없으므로 0이다. 따라서 Automatic Minimum Size는 둘의 최솟값인 0이다.

 

5. Resolve Flexible Lengths

우선 각 아이템들의 Outer Hypothetical Main Size를 구해보자. 첫번째와 세번째 요소는 N%( = 4N px)이지만, 두번째 요소에는 padding이 존재하기 때문에 min(N%, 40px)이 된다. 두번째 요소의 min 조건에 의해 세 요소의 크기가 달라지는 경계점이 존재하게 된다.

 

a. N ≥ 10일 때

세 아이템들의 Outer Hypothetical Main Size 합이 3N%인데 이 값이 Container Inner Main Size보다 큰지 작은지에 따라 flex shrink factor, flex grow factor가 사용될 것이다. 하지만, 현재 예시에서 flex shrink factor가 모두 0이므로 N > 30인 경우는 다루지 않는다. 10 ≤ N < 30일 때 세 아이템들의 크기 합은 Container Inner Main Size보다 작으므로 각 아이템들의 flex grow factor를 사용한다.

Initial Free Space는 400px에서 세 요소의 outer size의 합 3N%을 뺀 (400 - 12N)px이다. 이 값을 세 요소가 균등하게 가져가게 되므로 세 요소의 크기는 모두 동일하다.

 

a. N < 10일 때

세 아이템들의 Outer Hypothetical Main Size 합은 2N% + 40px인데 이 값은 당연히 Container Inner Main Size보다 작으므로 flex grow factor가 사용된다. Initial Free Space는 400px에서 세 요소의 outer size의 합을 뺀 (360 - 8N)px로 동일하지만 두번째 아이템의 Outer Hypothetical Main Size가 더 크므로 세 요소의 크기가 달라지게 된다.

 

6. Fix min/max Violations

해당 사항 없음

 

7. Freeze over-flexed items

6에서 변동 사항이 없으므로 Total Violation = 0이다. 따라서 모든 아이템을 freeze하고 종료한다.

 

 

B. flex: 0 1 N%일 때

1. 플렉스 아이템들이 차지할 수 있는 공간(Container Inner Main Size)

400px

 

2. Flex Base Size

`box-sizing: border-box`이므로 N%는 inner flex base size + padding + margin이 된다. 따라서, 첫번째와 세번째 요소의 Flex Base Size는 N%, 두번째 요소는 양쪽의 padding을 제외한 N% - 40px이다.

 

3. Hypothetical Main Size

플렉스 아이템에 min-width, max-width가 설정되어 있지 않으므로 Flex Base Size에 적용할 제약 조건이 없다. 다만, 두번째 요소에는 padding이 존재하므로 두번째 요소의 크기는 40px보다 작을 수 없다. 즉, min(N%, 40px).

 

4. Automatic Minimum Size

세 요소 모두 width가 없으므로 Specified Size Suggestion는 `undefined`, Content Size Suggestion는 `min-content`이지만 내부 요소가 없으므로 0이다. 따라서 Automatic Minimum Size는 둘의 최솟값인 0이다.

 

5. Resolve Flexible Lengths

우선 각 아이템들의 Outer Hypothetical Main Size를 구해보자. 첫번째와 세번째 요소는 N%( = 4N px)이지만, 두번째 요소에는 padding이 존재하기 때문에 min(N%, 40px)이 된다. 이 예시에서는 flex shrink factor가 적용되는 경계점은 3N% ≥ 400px 또는 2N% + 40px ≥ 400px이므로 N  33.33...이다. N < 33.33...보다 작아지게 되면 두번째 요소의 min 조건에 의해 나머지 요소의 크기와 달라질 수 있다.

 

와 두번째 요소의 Flex Base Size가  생각하면 된다. 

 

a. N < 10일 때

flex grow factor가 적용되는데 그 값이 모두 0이므로 Free Space를 분배하지 않는다. 첫번째와 세번째 요소의 크기는 N%이지만, 두번째 요소의 크기는 40px이므로 세 요소의 크기가 다르다.

 

b. 10 ≤ N < 33.33...일 때

flex grow factor가 적용되는데 그 값이 모두 0이므로 Free Space를 분배하지 않는다. N% > 40px이므로 세 요소의 크기는 모두 동일하다.

 

c. N 33.33...일 때

flex shrink factor가 적용되는데 Initial Free Space는 400px에서 세 요소의 outer size의 합 3N%을 뺀 (400 - 12N)px이고, 이 값은 0보다 작다. 아래와 같은 계산을 통해 세 요소의 크기를 조정한다.(첫번째, 두번째, 세번째 크기 순서로, px 단위이다.)

 

① Inner Flex Base Size은 각각 (4N)px, (4N - 40)px, (4N)px이고, 그 합은 (12N - 40)px이다.

② 합계에 대한 각 크기의 비율에 Initial Free Space (400 - 12N)px를 곱해서 나온 결과를 ①에 더해준다.

③ Outer Hypothetical Main Size는 ②의 결과에 두번째 요소의 padding 40px을 더해주면 된다. 

 

세 요소의 크기를 세부적인 계산 없이 문자로 치환해서 비교해보면 첫번째와 세번째 요소의 크기는 A + kA, 두번째 요소의 크기는 A + kA - 40k이다.(단, A > 0이고, k < 0) k < 0이기 때문에 두번째 요소의 크기는 나머지 요소의 크기와 반드시 다르다.

 

 


앞서 살펴본 대로 플렉스 아이템들의 크기가 모두 동일하도록 레이아웃을 구성하려고 할 때, 특정 아이템에만 padding이 존재한다면 Flex 레이아웃 상에서는 원하는 결과를 얻지 못할 수 있다. 이런 상황에서는 Flex 레이아웃 대신 Grid 레이아웃을 사용하는 편이 더 나은 선택일 것이다. 

 

 

 

3. 아이템의 크기가 유동적인 가로 스크롤 컨테이너

위 사진처럼 플렉스 레이아웃으로 각 아이템의 크기가 고정되지 않은 상황에서 가로로 스크롤을 할 수 있는 컨테이너 요소를 만든다고 해보자. CSS는 아래처럼 매우 간단하다. 이 간단한 레이아웃에서 각 플렉스 아이템의 크기가 어떻게 결정되는지 CSS Flex 아이템 크기 결정 알고리즘으로 분석해보면서 CSS Flex를 이해해보자.

.container {
  display: flex;
  overflow-x: auto;
  width: 400px;
  padding: 10px;
}

.item {
  padding: 30px;
}

한편, 각 플렉스 아이템에 들어간 텍스트는 아래와 같다.

const RANDOM_STRINGS = [
  "a",
  "bbb",
  "ccccccc",
  "dddddd ddddd",
  "eeeeeeeeeeeeeeee",
]

 

 

1. 플렉스 아이템들이 차지할 수 있는 공간(Container Inner Main Size)

`.container`의 width 400px에서 양쪽의 padding을 제외한 380px이 플렉스 아이템들이 차지할 수 있는 공간의 크기이다.

 

2. Flex Base Size

플렉스 아이템인 `.item` 요소에 `flex-basis`, `width` 모두 설정되어 있지 않으므로 `flex-basis: content`가 기본값으로 사용되어 Flex Base Size는 `max-content`이다.

 

3. Hypothetical Main Size

플렉스 아이템에 `min-width`, `max-width`가 설정되어 있지 않으므로 Flex Base Size에 적용할 제약 조건이 없다. 따라서 Flex Base Size와 같은 `max-content`이 된다.

 

4. Automatic Minimum Size

눈치가 빠른 사람이라면 사진에서 내부 텍스트가 `"dddddd ddddd"`인 요소의 텍스트가 왜 줄바꿈 되었는지 의문이 들었을지도 모르겠다. 그 이유는 아직 더 살펴봐야 이해할 수 있지만 일단은 이 Automatic Minimum Size 때문이라고 생각하고 넘어가자. 

 

플렉스 아이템에 `width`가 없으므로 Specified Size Suggestion는 `undefined`, Content Size Suggestion는 `min-content`이다. 따라서 Automatic Minimum Size는 둘의 최솟값인 `min-content`이다.

 

5. Resolve Flexible Lengths

우선 각 아이템들의 Outer Hypothetical Main Size를 구해보자. 양쪽의 padding 60px과 텍스트의 너비를 더한 값이 Outer Hypothetical Main Size이므로 첫번째 아이템 `"a"`부터 각각 68.3px  86.7px  114.4px  161.4px  195.0px이다.(사용자 화면에 따라 다를 수 있음.)

 

Container Inner Main Size는 380px인데 아이템들의 Outer Hypothetical Main Size 합은 625.8px로 이 값을 초과하므로 각 아이템들의 flex shrink factor를 사용한다. 플렉스 아이템에 `flex-shrink` 속성도 설정되지 않았으므로 기본값인 1이 사용된다.

 

Remaining Free Space는 380px에서 625.8px를 뺀 -245.8px이다. flex shrink factor를 사용하므로 각 플렉스 아이템의 Inner Flex Base Size의 비율을 구해야 하는데 Inner Flex Base Size는 앞서 구한 Outer Hypothetical Main Size에 padding 60px을 제외한 8.3px  26.7px  54.4px  101.4px  135.0px이고, 그 합은 325.8px이다. Remaining Free Space를 그 비율에 따라 나눠서 Outer Flex Base Size에서 빼면 각각 62.0px  66.6px  73.4px  84.9px  93.1px이고 이 값이 잠정적인 Outer Target Main Size이다. 예를 들어, 첫번째 아이템의 Outer Flex Base Size는  68.3px이고 scaled flex shrink factor의 비율은 `8.3 / 325.8`이므로 이 비율에 Remaining Free Space를 곱해서 배분해주면 `68.3 - (8.3 / 325.8) * 245.8 = 62.0`이다.

 

6. Fix min/max Violations

4에서  Automatic Minimum Size는 둘의 최솟값인 `min-content`임을 구했기 때문에 이 값으로 used min size를 구하면 각각 68.3px  86.7px  114.4px  113.3px  195.0px이다.(단어 사이에 공백이 있는 `"dddddd ddddd"`는 줄바꿈된 텍스트의 너비가 `min-content`이다.) 5에서 구한 Outer Target Main Size가 전부 used min size를 위반했으므로 모두 min violation이다.

 

7. Freeze over-flexed items

6에서 구한 clamped size와 5에서 구한 unclamped size의 차이를 모두 더한 Total Violation > 0이므로 min violation인 플렉스 아이템을 freeze 처리한다. 그런데 모든 아이템들이 min violation이므로 frozen 상태이므로 더이상 너비를 수정하지 않고 종료한다. 

 

 

 

 

A. min-width: 0 유무의 차이

둘의 차이를 더 확실하게 구분하기 위해 플렉스 컨테이너에서 `overflow-x: auto` 속성을 제거했다. 플렉스 아이템에 `min-width: 0`을 설정한 경우에는 텍스트의 길이가 긴 아이템은 텍스트가 overflow되었지만, `min-width`을 설정하지 않은 경우에는 아이템의 크기가 텍스트의 길이에 맞게 온전히 렌더링되었음을 볼 수 있다. 둘의 차이는 고작 `min-width: 0`일 뿐인데 어째서 이렇게 레이아웃이 완전히 다른 상황이 벌어진 걸까?

 

여기서 각 플렉스 아이템은 scroll container가 아니다. 따라서 min(Specified Size SuggestionContent Size Suggestion)이 automatic minimum size로 사용되는데 Specified Size Suggestion는 `width`가 설정되어 있지 않기 때문에 기본값인 `max-content`, Content Size Suggestion는 `min-width ≤ min-content ≤ max-width`가 된다. 

Specified Size Suggestion = auto(기본값) = max-content
Content Size Suggestion = `min-width ≤ min-content ≤ max-width`

플렉스 아이템의 `flex-basis`는 기본값인 `width: auto` = max-content

 

 

 

플렉스 아이템에 `min-width: 0`를 설정하지 않으면 기본값으로 automatic minimum size가 사용되므로 Hypothetical Main Size의 min 제약 조건은 Content Size Suggestion. 즉, 아이템의 내부 텍스트 사이즈(`min-content`)이다. Hypothetical Main Size가 `min-content`이므로 텍스트가 플렉스 아이템에서 overflow 되지 않고 온전히 렌더링될 수 있다. 

 

반면에, 아이템에 `min-width: 0`를 설정하면 이 값이 automatic minimum size 대신 사용되므로 Hypothetical Main Size의 min 제약은 0이 된다.

 

** 이 예시에서 `overflow: auto`를 설정했을 때 `max-width: 400px` 조건이 있기 때문에 available free space는 400px일텐데 왜 전체 플렉스 아이템들이 400px을 넘는 공간을 차지할 수 있을까?

 

 

 

 

2. 컨테이너의 크기도 유동적이라면?

 

첫번째 예시에서는 컨테이너의 크기를 고정값 400px로 사용했다. 만약, 이 컨테이너 옆에 크기가 고정된 컨트롤 패널 있는 경우라면 어떨까? 간단하게 컨테이너의 크기를 `width: calc(100% - 50px)`로 설정하는 방법이 있을 것이다. 하지만 플렉스 레이아웃에 대해 더 깊게 이해하기 위해 가로 스크롤 컨테이너와 컨트롤 패널의 부모 요소에 `display: flex`를 적용해 가로 스크롤 컨테이너가 플렉스 아이템이자 플렉스 컨테이너가 되는 경우에 대해 생각해보자.