Block Formattion Context

BFC는 normal flow에서 블록 상자들이 배치됐을 때 웹 페이지를 렌더링하는 시각적 CSS의 일부이다.
추가적인 설명 페이지 : 박스 모델과 BFC의 이해

W3C에 따르면,

Floats, absolutely positioned elements, inline-blocks, table-cells, table-captions, and elements with ‘overflow’ other than ‘visible’ (except when that value has been propagated to the viewport) establish new block formatting contexts.

위의 글은 BFC가 형성되는 방식을 요약했다. 하지만 좀 더 이해하기 쉽게 설명해보자. BFC는 다음 조건들 중 적어도 하나를 만족시키는 HTML 박스이다.

  • float (float가 none이 아닌 요소)
  • 절대 위치를 지정한 요소(position이 absolute 혹은 fixed인 요소)
  • display 값이 table-cell, table-caption, inline-block, flex, inline-flex인 경우
  • overflow (overflow가 visible이 아닌 요소)

BFC 형성

BFC는 의도적으로 형성할 수 있다. 그래서 만약 새로운 BFC를 만들고 싶다면 위에서 설명한 BFC 형성 조건에 맞는 CSS 속성을 사용하면 된다.

예를 들어 설명해보자.

1
2
3
<div class="container">
Some Content here
</div>

overflow: scroll, overflow: hidden, display: flex. float: left, display: table 같은 css 속성을 이용해서 새로운 block formatting context를 형성할 수 있다. 이 속성들 중 하나라도 BFC를 만들 수 있지만 다음과 같은 효과들이 생긴다.

  • display: table는 반응형 웹에서 문제가 생길 수 있다.
  • overflow: scroll은 원치않는 스크롤바를 생성한다.
  • float: left는 요소를 왼쪽으로 보내면서 다른 요소들이 주변을 감싸게 된다.
  • overflow: hidden은 넘치는 요소를 숨긴다.

따라서, 우리는 새로운 BFC를 만들때, 우리의 요구조건에 맞는 최선의 속성을 선택해야 한다.
이 글에서는 통일성을 위해 모든 예시에서 overflow: hidden 속성만을 사용해서 설명하겠다.

BFC에서의 상자 정렬

먼저, W3C에서는 BFC에서의 박스 정렬을 어떻게 설명하고 있는지 알아보자.

In a block formatting context, each box’s left outer edge touches the left edge of the containing block (for right-to-left formatting, right edges touch). This is true even in the presence of floats (although a box’s line boxes may shrink due to the floats), unless the box establishes a new block formatting context (in which case the box itself may become narrower due to the floats).

위 글을 번역해보면,

BFC에서 각 상자의 왼쪽 변은 컨테이너 블록의 왼쪽 변에 닿아야 한다. (오른쪽에서 왼쪽 방향의 서식이라면 반대로 오른쪽 변에 닿아야 한다) 상자가 새 BFC를 형성하지 않는 한, float 상태일때도 마찬가지이다. (이 경우 float로 인해 상자 자체가 좁아질 수 있음)

번역글을 읽어도 잘 이해가 가지 않는다. 그림으로 살펴보자.

Block Formatting Context

간단히 말해서, 위의 그림에서 볼 수 있듯이 BFC에 속하는 모든 상자는 왼쪽으로 정렬되고 왼쪽 변이 컨테이너 블록의 왼쪽 변에 닿는다. 마지막에 float된 요소(갈색 상자)가 있음에도 불구하고 다른 요소(초록색 상자)의 왼쪽 변이 여전히 컨테이너 블록에 닿는다. 왜 이런 일이 발생하는지에 대한 원리는 다음 단락에서 살펴보도록 하자.

마진 병합을 야기하는 BFC

normal flow에서 상자들은 컨테이너 블록의 위에서부터 수직적으로 쌓이면서 배치된다. 두 형제 상자의 수직적 거리는 각 상자의 위, 아래 마진으로 결정된다. 하지만 두 상자의 위, 아래 마진의 합이 수직적 거리가 되는 것은 아니다.

그림을 보면서 이해해보자.

위 그림에서 빨간색 상자(div)에 두 개의 초록색 자식 상자(p)가 포함된 BFC가 생성된 것을 알 수 있다.

1
2
3
4
<div class="container">
<p>Sibling 1</p>
<p>Sibling 2</p>
</div>

그리고 해당되는 CSS는,

1
2
3
4
5
6
7
8
9
.container {
background-color: red;
overflow: hidden; /* creates a block formatting context */
}

p {
background-color: lightgreen;
margin: 10px 0;
}

이론적으로 두 자식 상자간의 마진은 위아래 마진을 합한 값인 20px이 돼야 하는데 실제로는 10px이 된다.
이것을 마진 병합이라고 한다.
만약 두 마진 값이 다르다면 더 큰 값이 적용된다.

BFC를 이용해서 마진 병합을 막는 방법

위에서 BFC가 마진 병합을 야기한다고 했는데 이번엔 BFC가 마진 병합을 막을 수 있다니 좀 혼란스러울 수 있다.

그러나, 우리가 놓치고 있는 개념이 있다.
두 형제 상자의 마진 병합은 이 상자들이 같은 BFC에 존재할 때만 일어난다. 만약 두 상자가 다른 BFC에 속한다면 마진은 병합되지 않는다. 따라서 새로운 BFC를 만들어준다면 마진 병합을 막을 수 있다.

예를 들어 설명해보자.

1
2
3
4
5
<div class="container">
<p>Sibling 1</p>
<p>Sibling 2</p>
<p>Sibling 3</p>
</div>

여기에 해당하는 CSS는,

1
2
3
4
5
6
7
8
9
.container {
background-color: red;
overflow: hidden; /* creates a block formatting context */
}

p {
background-color: lightgreen;
margin: 10px 0;
}

이 결과는 위에서 봤던 예제와 일치한다. 형제 상자간의 거리는 10px인데, 이는 세 개의 p태그가 같은 BFC에 존재하기 때문이다.

이제, 세 번째 상자를 새 BFC에 존재하도록 수정해보자.

1
2
3
4
5
6
7
<div class="container">
<p>Sibling 1</p>
<p>Sibling 2</p>
<div class="newBFC">
<p>Sibling 3</p>
</div>
</div>

이에 해당하는 CSS는,

1
2
3
4
5
6
7
8
9
10
11
12
13
.container {
background-color: red;
overflow: hidden; /* creates a block formatting context */
}

p {
margin: 10px 0;
background-color: lightgreen;
}

.newBFC {
overflow: hidden; /* creates new block formatting context */
}

결과를 살펴보자.

2번 상자와 3번 상자는 서로 다른 BFC에 속하기 때문에 마진 병합이 일어나지 않는다.

BFC를 사용해서 float를 포함

BFC는 float된 요소를 포함할 수 있다. 우리는 컨테이너가 float된 요소를 포함한 경우를 수도 없이 많이 봐왔다. 이 경우, 컨테이너 상자가 높이를 잃어버리고 float된 요소는 normal flow 상태를 벗어나 위로 뜨게 된다. 우리는 일반적으로 이를 해결하기 위해 clear fix를 사용한다.

하지만, 우리는 BFC를 정의해 이를 해결할 수도 있다.

예제를 통해 살펴보자.

1
2
3
4
<div class="container">
<div>Sibling</div>
<div>Sibling</div>
</div>

이에 해당하는 CSS는,

1
2
3
4
5
6
7
8
9
.container {
background-color: green;
}

.container div {
float: left;
background-color: lightgreen;
margin: 10px;
}

이 예제에서는 컨테이너가 float 요소를 포함하지 않아 높이를 잃어버린다.
우리는 컨테이너 상자 안에 새로운 BFC를 형성해 이를 해결할 수 있다.

1
2
3
4
5
6
7
8
9
10
.container {
overflow: hidden; /* creates block formatting context */
background-color: green;
}

.container div {
float: left;
background-color: lightgreen;
margin: 10px;
}

이제 컨테이너는 float된 요소를 포함하고 높이를 되찾는다. float된 요소들은 새로운 BFC 안에서 normal flow 상태로 돌아온다.

BFC를 사용하여 text wrapping 방지

요소를 float 시키면 주변 텍스트들이 요소를 감싸게 된다. 그러나, 몇몇 경우에 이 현상을 원치 않고 Figure2 같은 모습을 원할 수 있다.

이는 마진을 이용해 해결할 수 있지만, BFC를 사용해 해결할 수도 있다.

먼저, 왜 텍스트가 요소를 감싸는지 이해해보자. 이를 이해하기 위해 요소가 float 됐을 때 박스 모델이 어떻게 작동하는지 이해할 필요가 있다.

아래 그림에서 어떤 일이 일어났는지 이해해보자.

1
2
3
4
5
6
7
8
9
10
<div class="container">
<div class="floated">
Floated div
</div>
<p>
Quae hic ut ab perferendis sit quod architecto,
dolor debitis quam rem provident aspernatur tempora
expedita.
</p>
</div>

위 그림에서 검정색 영역에 p태그 영역이다. 보시다시피 p태그 요소는 이동하지 않고 float된 요소 아래로 이동한다. p 요소의 라인 박스가 float 요소의 공간을 만들기 위해 이동하여 라인 상자가 수평으로 좁아진다.

텍스트 양이 늘어나면, 라인 박스가 더 이상 이동할 필요가 없으므로 Figure1 과 같은 모양이 된다. 이는 float된 요소가 있는 경우에도 박스가 컨테이너 블록의 왼쪽 변에 닿는 방식과 라인 박스가 float 요소를 포함하기 위해 좁아지는 방식을 설명한다.

만약, P태그 전체를 움직일 수 있으면 이 문제를 해결할 수 있다.
이 문제를 해결하기 앞서 W3C에서 뭐라고 했는지 상기해보자.

BFC에서 각 상자의 왼쪽 변은 컨테이너 블록의 왼쪽 변에 닿아야 한다. (오른쪽에서 왼쪽 방향의 서식이라면 반대로 오른쪽 변에 닿아야 한다) 상자가 새 BFC를 형성하지 않는 한, float 상태일때도 마찬가지이다. (이 경우 float로 인해 상자 자체가 좁아질 수 있음)

이에 따르면, 만약 p요소가 새로운 BFC를 형성한다면 더 이상 컨테이너 블록의 왼쪽 변에 닿아있지 않아도 된다. 이는 간단하게 overflow: hidden을 P요소에 추가하여 해결할 수 있다.

새로운 BFC를 형성해서 float로 인해 텍스트가 요소를 감싸는 문제를 해결할 수 있다.

Multi-column 레이아웃에서 BFC의 사용

컨테이너 전체 너비를 사용해서 다중 열 레이아웃을 만드는 경우, 일부 브라우저에서 마지막 열이 다음 줄로 떨어질 수 있다. 이것은 브라우저가 열의 너비를 반올림해서 총 너비가 컨테이너의 너비보다 커지기 때문에 발생할 수 있다. 그러나 레이아웃의 열에 새로운 BFC를 설정하면 항상 이전 열이 채워진 후 남은 공간을 차지하게 된다.

3 개의 열을 가진 레이아웃을 예시로 들어보자.

1
2
3
4
5
<div class="container">
<div class="column">column 1</div>
<div class="column">column 2</div>
<div class="column">column 3</div>
</div>

이에 해당하는 CSS는,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.column {
width: 31.33%;
background-color: green;
float: left;
margin: 0 1%;
}

/* Establishing a new block formatting
context in the last column */
.column:last-child {
float: none;
overflow: hidden;
}

이제 컨테이너의 너비가 약간 변경되어도 레이아웃이 깨지지 않는다. 물론, 이 방법은 multi-column 레이아웃에서 최선의 방법은 아니지만 마지막 열이 떨어지는 문제를 해결하는 하나의 방법이 될 수 있다.

flex를 사용한다면 더 나은 해결 방법이 될 수 있지만 BFC를 이해하기 위한 하나의 예제로 사용해봤다.

Reference