css를 공부하면서 개인적으로 잘 와닿지 않았던 개념 중 하나가 Block Formatting Context (BFC)
입니다.
사실 이 개념이 적용된 코드를 보았을 때는 그렇게 어렵게 느껴지지 않았는데요.
막상 개념 자체를 제 스스로에게 설명 해보려고 하니, 어떤 식으로 설명을 해야할지 감이 안 잡히더라구요.
그래서 w3c 스펙과 mdn 문서를 참고로 하여,
bfc란 무엇이고, 이것이 어떻게 동작하며, 어떤 문제를 해결할 수 있는지에 대해 정리해보고자 합니다.
해당 개념을 설명하기 앞서, Normal flow
라는 개념에 대해 알고 있어야 합니다.
Normal flow란, 레이아웃이 변경되지 않은 기본 html 마크업만을 가지고 레이아웃을 배치했을 때 나타나는 레이아웃 방식을 말합니다.
보통 css로 레이아웃과 관련된 속성을 별도로 변경하지 않을 경우, 여기에 해당한다고 볼 수 있습니다.
w3c 스펙에 보면, normal flow에 관해 다음과 같이 적혀 있습니다.
Boxes in the normal flow belong to a formatting context, which may be block or inline, but not both simultaneously. Block-level boxes participate in a block formatting context. Inline-level boxes participate in an inline formatting context.
일반적인 문서의 배치 흐름에서 박스들은 formatting context
에 속합니다. 블록 레벨 박스는 block formatting context에 참여하고, 인라인 레벨 박스는 inline formatting context에 참여한다고 정의되어 있습니다.
여기서 formatting context 라는 처음 보는 새로운 용어가 등장하는데요.
이에 대해 설명하기 전에, 위에 언급되어 있는 각각의 formatting context에 대해 조금 더 알아보도록 하겠습니다.
In a block formatting context, boxes are laid out one after the other, vertically, beginning at the top of a containing block.
In an inline formatting context, boxes are laid out horizontally, one after the other, beginning at the top of a containing block.
bfc에서 박스들은 자신의 컨테이닝 블록의 상단부터 시작해서 수직으로 배치되고, ifc에서는 수평으로 배치된다고 정의되어 있습니다.
정리해보면,
문서에 나타나는 모든 박스들은 formatting context의 한 부분이며, 어떤 formatting context에 참여하는지에 따라, 각 박스들이 문서에 배치되는 방식이 달라진다고 볼 수 있습니다.
formatting context 라는 것은 박스들을 어떻게 배치할 것인지를 정한 규칙 같은 것이라고 볼 수 있으며, 블록 레벨 박스는 bfc에 참여한다고 되어 있으니, 블록 레벨 박스는 블록 박스와 인라인 박스를 모두 포함할 수 있으므로, 인라인 레벨 박스가 참여하는 ifc는 bfc 내에서 생성될 것이라고도 추측할 수 있습니다.
여기서 혼동하면 안되는 것이 있는데, 블록 레벨 박스가 bfc에 참여한다는 것을, 블록 레벨 박스가 bfc를 형성한다고 생각하는 것입니다. 둘은 다른 개념이고, 인라인 레벨 박스와 ifc도 마찬가지입니다.
다음은 bfc가 언제 형성되는지에 대해 알아보겠습니다. 이 부분은 w3c 스펙보다 mdn 문서에 더 자세히 나와있습니다.
문서의 루트 요소인 html은 bfc를 형성합니다.
그런데, html 요소만이 bfc를 형성할 수 있는 것은 아닙니다.
일부 css 속성을 적용하면, 루트 요소 외의 다른 요소에도 새로운 bfc를 형성할 수 있도록 할 수 있습니다.
그 중에서 자주 사용되는 속성 몇가지만 가져와봤습니다.
이 외에도 많습니다. 어떤 속성을 사용해야 하는지는 상황에 따라 맞춰 사용하면 됩니다.
여기서부터는, 궁금한 점이 하나씩 생깁니다.
아래서부터는 이 의문에 답을 해보려고 합니다. 스펙의 내용을 가지고 제 나름대로 해석을 해본 것이므로 다소 틀릴 수도 있습니다.
앞서 언급한 w3c 스펙에서 bfc와 관련된 부분을 다시 가져와봤습니다.
In a block formatting context, boxes are laid out one after the other, vertically, beginning at the top of a containing block.
bfc에서 박스들은 자신의 컨테이닝 블록 상단부터 시작해서 수직으로 배치된다고 정의되어 있습니다.
모든 박스는 컨테이닝 블록이란 것을 가지고 있습니다.
이 컨테이닝 블록은 각 요소의 크기와 위치를 지정하는데 영향을 미치는 박스 영역으로, 기본적으로 position 속성값이 absolute, fixed가 아닌 경우, 가장 가까운 블록 박스나 bfc를 형성하는 박스가 됩니다.
html 문서에서 루트 요소인 html은 초기 컨테이닝 블록으로서, 모든 요소의 크기와 위치를 결정하는데 기본이 되는 박스입니다.
따라서 문서의 루트 요소가 bfc를 형성하게 되면, 다음과 같은 일이 벌어집니다.
html 문서의 컨텐츠를 표시하는 body 박스의 컨테이닝 블록은 초기 컨테이닝 블록인 html이 되므로, body는 html의 상단에 수직으로 배치됩니다.
그런데 body 밑에 다른 박스들이 또 있겠죠? 이 박스들의 position 속성값이 absolute, fixed가 아니라면 body 박스가 컨테이닝 블록이 되므로, body 박스의 상단부터 시작해서 수직으로 배치될 것입니다. 같은 방식으로 밑에 다른 박스가 또 있다면 이 과정이 계속해서 반복됩니다.
결국 html 요소 밑에 있는 모든 박스는 html 박스 영역의 상단부터 수직으로 배치됩니다. 즉, html 요소 밑에 있는 모든 박스들이 html 요소 안에 포함되게 됩니다.
하나의 문서는 하나의 html 요소를 가지므로, 그 외의 다른 요소에 bfc를 형성한다는 것은 bfc 안에 또다른 bfc를 만든다는 것을 의미합니다.
그런데 새로운 bfc는 왜 만드는 것일까요? 새로운 bfc가 형성되면 뭐가 다를까요? 한번 그 차이를 직접 확인해보도록 하겠습니다.
html 요소 안에 A요소에는 bfc를 형성하지 않고, 다른 요소들에는 새로운 bfc를 형성하도록 코드를 추가했습니다.
단지 새로운 bfc를 형성했을 뿐인데, 한가지 특징을 발견할 수 있습니다.
bfc를 형성하는 요소 밑에 있는 모든 박스들이 bfc가 형성된 요소의 영역에 배치된다는 것입니다.
html 요소 밑에 있는 박스는 모두 html 박스 영역의 상단부터 수직으로 배치되고, 새로운 bfc를 형성한 요소 밑에 있는 박스는 다시 그 영역의 상단부터 수직으로 배치되는 것이 눈에 보이시나요?
w3c 스펙에도 이와 같은 특징에 대해 설명하고 있습니다.
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).
정리해보면,
새로운 bfc를 형성한다는 것은 결국 새로운 레이아웃 영역을 만드는 것과 같다고 할 수 있습니다.
특정 박스에 bfc가 형성됨으로써 그 밑의 모든 박스들을 포함할 수 있는 영역이 생성되고, 그 영역 안에서 박스들이 배치되도록 만드는 것이죠.
새로운 bfc가 형성되지 않는 한, 모든 박스는 html 요소가 형성한 bfc에 참여할 것입니다.
이와 같이 같은 bfc에 참여하는 박스들에 대해, w3c 스펙은 다음과 같이 정의하고 있습니다.
The vertical distance between two sibling boxes is determined by the 'margin' properties. Vertical margins between adjacent block-level boxes in a block formatting context collapse.
같은 bfc에 속한 인접한 블록 요소 사이의 거리는 margin
프로퍼티로 결정되며, 상하 마진 병합 현상이 발생합니다.
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 박스가 존재하면, 그에 인접해있는 형제 박스는 float 박스가 있던 곳으로 들어가지 않고 오히려 자신의 영역을 줄이면서 float 박스를 위한 공간을 만들어줍니다.
이때 형제 박스의 영역을 다 담을 수 없게 되면, 넘쳐나는 부분은 컨테이닝 블록의 가장 왼쪽으로 옮겨서 배치되면서 마치 형제 박스가 float 박스를 감싸주는 듯이 보이게 됩니다.
하지만, 위에 나타난 margin, float과 같은 css 속성을 이용해 박스를 배치하고자 하면, 솔직히 속성 적용이 많이 까다롭습니다. 마진 병합 같은 경우가 특히 그러하죠.
바로 이러한 문제들을 해결하기 위해, 새로운 bfc를 형성합니다.
bfc가 만들어낸 문제를 새로운 bfc로 해결한다고도 볼 수 있죠. 살짝 아이러니하지만...
아래는 새로운 bfc가 해결하는 문제 상황을 간단히 예시 코드로 작성해보았습니다.
아래 코드는 bfc를 각각 다르게 형성하여, 상하 마진 병합 현상이 어디에서 일어나는지 확인할 수 있도록 했습니다.
float 박스와 형제 박스 둘다 bfc를 형성하면, 각각의 그 밑에 있는 박스들은 항상 bfc 영역의 왼쪽 가장자리에 닿으려고 하기 때문에, 서로의 영역을 침범하지 않습니다.
즉, 앞서 언급했던 형제 박스가 float 박스를 감싸는 현상이 사라집니다.
이때 부모 박스에도 bfc를 형성하도록 해야 부모의 영역이 float 박스와 형제 박스 영역을 포함할 수 있습니다.
결국, 부모 박스와 자식 박스 모두가 bfc를 형성하여 각각의 레이아웃 영역이 생성된 것을 알 수 있습니다.
bfc는 단지 문서에 박스를 배치하는 규칙으로서, 박스를 배치할 수 있는 영역을 만듭니다.
그리고 기존에 우리가 익숙하게 알고 있던 박스들 있죠? 블록 레벨 박스와 인라인 레벨 박스들. 그 박스들이 그 영역 안에 배치되는 것입니다.
다소 추상적인 개념일 수 있지만, formatting context
를 이해하면 문서에 배치된 박스들이 왜 그런 식으로 배치되는지 이해하는데 많은 도움이 됩니다.
이 글을 읽고 있는 분들께 제 나름의 논리를 펼쳐 해석해본 bfc의 의미가 잘 전달됐으면 좋겠습니다.