float, 딥 다이브!

💛 nalsae·2022년 7월 18일
5

💙 내보정 CSS

목록 보기
5/7
post-thumbnail

😂 이게 왜 이렇게 되지..?

 CSS를 사용하면서 만날 수 있는 대표적인 이슈 중 하나로 float를 레이아웃 배치에 사용하는 경우에 발생하는 이슈가 있다. 실제로 float를 레이아웃 배치에 많이 사용하고 있지만, 사실 float는 레이아웃 배치를 목적으로 만들어진 속성은 아니다.


🤨 float, 넌 누구냐!

 먼저 이슈의 원인을 분석하기 전에 float의 원래 목적과 사용법에 대해 알아보고자 한다. 본래 float텍스트와 이미지를 적절하게 배치하는 도구로써 탄생했다. Microsoft의 Word를 사용할 때 이미지를 삽입하다 보면, 한 번쯤 이런 아이콘을 본 적이 있을 것이다.

 바로 이것이 float의 목적이다. float는 위와 같이 텍스트와 이미지를 배치하는 방식을 다루는 속성인 것이다. MDN 사이트의 예제를 살펴보면 이를 잘 알 수 있다.

💡 float: none

💡 float: left

💡 float: right

 이 외에도 inline-startinline-end로 값을 설정할 수도 있다. 위의 예제에서도 볼 수 있듯이 float을 사용하면 block 요소처럼 줄 바꿈이 발생했던 배치 방식을 이미지와 텍스트가 적절히 조화된 것처럼 변화시킬 수 있다. 그렇다면 어떤 원리로 이런 동작이 가능한 것일까?


🔍 float를 파헤쳐보자!

 이름 그대로 float를 적용한 요소는 위로 붕 떠버린다. 무슨 말인지 정확히 이해하기 어렵기 때문에 이해를 돕기 위한 이미지 하나를 첨부하면 다음과 같다.

 우리는 페이지를 2차원 평면에서 수직적으로 바라보고 있기 때문에 float의 원리를 이해하기 어렵지만, float를 적용하는 순간 위의 이미지처럼 normal flow를 벗어나서 위에 붕 뜨게 된다고 볼 수 있다.

 그런데 여기서 의문이 하나 생긴다. 저 원리대로라면 normal flow가 적용되는 텍스트는 float 된 요소 밑에 일부분 가려진 상태로 보여져야 하지 않는가? 그렇다면 float 되었을 때 원래 있던 normal flow 요소는 위의 그림처럼 자동으로 영역이 재지정되는 것일까?

 답은 '그렇지 않다.'이다. float가 탄생하게 된 배경을 잘 생각해보면 알 수 있다. 앞서 살펴봤듯 float는 본래 텍스트와 이미지의 배치 방식을 조절하기 위해 탄생했다. 그러므로 float를 적용할 때 텍스트가 가려지면 그 의의가 상실되는 것이나 다름없다. 구체적으로는 float를 사용하면 외부의 BFC가 float 요소의 left, top 값의 정보를 저장한다. 그리고 BFC는 float 요소의 숙명을 지켜주기 위해 float와 연관된 block 요소 내부의 inline 콘텐츠를 파악한 뒤, 저장해두었던 float 요소의 left, top 값에 맞게 IFC로 변경하여 내부 콘텐츠를 배치하는 것이다.

 BFC에 관한 이야기는 후술할 float 속성 사용 관련 이슈에서 좀 더 자세하게 다뤄보도록 하고, 다시 돌아와서 float된 요소와 연관된 block 요소의 영역 이야기를 해보고자 한다.

 내부의 inline 콘텐츠가 자동으로 float된 요소의 위치에 맞게 배치되어서 normal flow 영역의 크기가 재지정된 것처럼 보일 수 있겠지만, 실상은 그렇지 않다. 앞서 살펴봤던 그림처럼 float된 요소는 그와 연관된 normal flow의 block 요소 위에 떠 있을 뿐이다. 이를 시각적으로 증명하기 위해 아래의 block 요소에 border 속성을 설정했다고 가정해보자. 만약 영역이 재지정된다면 float된 요소를 제외한 텍스트 영역에 border 처리가 되어야 할 것이다. 그러나 border는 위의 그림에서 빨간 테두리 영역처럼 보여진다. 즉, block 요소는 그대로 있고, float를 적용한 요소만 normal flow를 벗어나서 block 요소 위에 붕 뜨게 된다는 것을 알 수 있다.


😡 말도 많고 탈도 많은 float

 앞서 언급했듯이 float는 본래 그 목적과 달리 실제로 레이아웃을 배치하기 위해서 많이 사용되는 속성이다. 그런데 원래의 목적이 아닌 방식으로 사용하다 보면 예기치 못한 이슈가 발생하기 마련이다. 이러한 이슈를 다음의 예시와 함께 살펴보고자 한다.

  <main class="main">
    <div class="group group1">그룹 1</div>
    <div class="group group2">그룹 2</div>
    <div class="group group3">그룹 3</div>  
  </main>
  <article class="another">그룹 밖</article>
.main{
  background: rgb(243, 156, 240);
  border: 12px solid rgb(89, 51, 110);
}
.group{
  background: rgb(185, 143, 205);
}
.group1{
  background: rgb(203, 78, 78);
}
.group2{
  background: rgb(237, 207, 73);
}
.group3{
  background: rgb(57, 155, 100);
}
.another{
  background: lightblue;
  height: 100px;
}

 위와 같은 구조의 페이지가 있다고 가정해보자. 만약 여기서 '그룹 1' divfloat를 부여하면 '그룹 1' div는 더 이상 normal flow에 따르지 않고, 붕 뜨게 되어 다음과 같은 상태가 된다.

.group1{
  background: rgb(203, 78, 78);
  float: left;
  height: 100px;
}

 시각적으로 좀 더 쉽게 파악하기 위해서 '그룹 1' divheight 값을 100px로 설정해보았다. 그림을 보면 알 수 있듯이, '그룹 1' divfloat를 설정함으로써 그 근처에 있는 inline 요소가 전부 영향을 받는다.

 그렇다면 만약 main의 하위 요소인 '그룹 1', '그룹 2', '그룹 3'에 전부 float 속성을 부여하는 경우 어떻게 될까?

 어떤 이슈가 발생하는지 시각적으로 설명하기 위해 '그룹 1' div에 설정했던 height 값도 일단 제거해보았다. 모든 하위 요소에 float 속성을 부여하면 위와 같이 다소 당황스러운 결과를 맞닥뜨리게 된다. 바로 부모 요소인 main의 크기 자체가 사라지게 되는 것이다.

 영역의 구분을 위해 main에 설정한 border 속성을 제거하면 위의 그림처럼 보여진다.

.main{
  background: rgb(243, 156, 240);
}

 분명히 main 요소에 배경색을 설정했음에도 불구하고 요소의 크기 자체가 사라졌기 때문에 설정한 배경색이 정상적으로 보이지 않는 것을 알 수 있다. 이처럼 예상치 못한 이슈가 발생하는 이유는 무엇일까? 바로 main의 하위 요소에 float 속성을 지정하면서 normal flow에서 벗어났기 때문이다.

 구체적으로 설명하면, block 요소의 width 값과 height 값을 따로 설정해주지 않았을 때 기본 값은 auto이므로 normal flow를 따르는 하위 요소의 크기로 정해진다. 그러나 하위 요소 전부에 float를 적용함으로써 더 이상 normal flow를 따르는 하위 요소가 존재하지 않게 되고, 결국 mainwidth, height 값은 0이 되어 보이지 않는다.


⭐ float 이슈 해결, 그 전에 잠깐!

 그렇다면 이러한 float 이슈를 어떻게 해결할 수 있을까? 다양한 방법이 있지만, 먼저 그 전에 앞서 언급했던 BFC를 잠깐 살펴보고자 한다.

 이전에 display를 다루는 글에서 BFC는 block 요소를 배치하는 normal flow의 방식 중 하나, 혹은 그 과정에서 암묵적으로 생성되는 block 레벨의 레이아웃이라고 소개한 적이 있었다. 이 중에서 후자가 바로 지금 살펴보고 있는 이슈의 해결에 관여한다.

💡 BFC가 생성되는 상황

  • 문서의 루트 요소(<html>)

  • 플로팅 요소(float 값이 none이 아닌 경우)

  • 절대 위치를 지정한 요소(position 값이 absolute 또는 fixed인 경우)

  • 인라인 블록(displayinline-block)

  • 표 칸(displaytable-cell)

  • 표 주석(displaytable-caption)

  • displaytable 요소, 혹은 table의 하위 요소인 경우

  • overflowvisible이 아닌 block 요소

  • displayflow-root인 경우

  • containlayout, content, paint인 경우

  • displayflex인 요소의 하위 요소

  • displaygrid인 요소의 하위 요소

  • column-count, column-widthauto가 아닌 경우

  • column-spanall인 경우

 MDN에서 BFC 관련 내용을 찾아보면, 위과 같이 다양한 경우에 BFC가 생성된다고 명시하고 있다. 여기서 주목할 부분은 바로 '플로팅 요소'이다. 이 글에서 다루는 float 역시 속성을 지정하면 암묵적인 block 레벨 레이아웃이 생성되는 것이다.

 그렇다면 여기서 의문이 하나 생긴다. BFC를 통해 암묵적인 block 레벨 레이아웃이 생성된다면, 보이지 않는 block 요소로 감싸져 있는 것일까? 그렇지는 않다. BFC는 block 요소를 배치하는 방식, 혹은 암묵적으로 생성되는 레이아웃일 뿐이지 block 요소 자체가 아니기 때문이다.

 BFC와 block 요소가 굉장히 혼동하기 쉬운 개념이라는 생각이 드는데, 본래 BFC가 레이아웃뿐만 아니라 normal flow의 배치 방식이기도 하다는 점에 초점을 맞춰 생각할 필요가 있다. 이론적으로만 바라보면 직관적인 이해가 어렵기 때문에 한 가지 예시를 통해 BFC를 좀 더 쉽게 이해해보고자 한다.

 아까 전의 예시에서 '그룹 1' div에만 height 값을 100px로 설정했다. 만약 여기서 브라우저 창의 크기를 세로로 100px보다 축소한다면 어떻게 될까?

 스크롤이 생기면서 '그룹 1' div는 브라우저 창 밖으로 튀어나오지 않는다. 너무 당연한 예시인 것 같지만, 여기에 바로 BFC가 관여한다.

 아까 소개했던 BFC가 생성되는 상황을 다시 한 번 잘 읽어보면, 첫 번째 줄에 '문서의 루트 요소(<html>)'라는 말이 보인다. 즉 태초의 HTML 문서는 아무런 CSS 속성의 지정 없이도 암묵적인 block 레벨 레이아웃을 가진다.

 그렇다면 BFC 안에서는 무슨 일이 일어나길래 HTML 문서의 창 크기가 작아지더라도 float된 요소가 튀어나오지 않는 것일까? 결론부터 말하면, BFC는 float된 요소를 포함하여 BFC 안에 있는 요소의 좌표 값을 모두 기억하고 있다. 그렇기 때문에 브라우저 창이 작아지더라도 기억한 좌표 값을 토대로 float된 요소가 창 밖으로 튀어나오지 않도록 조치를 취할 수 있는 것이다.

 쉽게 생각해보면 결국 BFC가 생성되는 것은 하나의 작은 <html> 페이지 레이아웃을 생성하는 것이라고 볼 수 있겠다.


🔸 overflow 이용하기

 그렇다면 지금까지 살펴본 BFC에 대한 이해를 토대로 float 이슈를 어떻게 해결할 수 있는지 살펴보고자 한다. 먼저 부모 요소에 overflow 속성을 hidden, 또는 scroll로 지정해주는 방법이 있다.

.main{
 background: rgb(243, 156, 240);
 overflow: hidden;
}

 다른 요소는 전혀 건드리지 않고 부모 요소인 mainoverflow 속성 값만 hidden으로 설정했다. 그런데 위의 그림처럼 main 요소가 크기를 잃었던 이슈가 말끔히 해결되고, 설정해놓은 background 속성이 정상적으로 적용되는 것을 확인할 수 있다.

 여기서 뭔가 익숙하다고 느껴지지 않는가? 그렇다. 앞서 살펴본 BFC가 생성되는 상황에는 'overflowvisible이 아닌 block 요소' 역시 포함된다. 즉 mainoverflow를 지정하면 main은 하나의 작은 <html>이 되면서, BFC에 따라 그 안에 있는 float된 자식 요소의 좌표 값을 알 수 있게 되는 것이다. 좌표 값을 알게 되면 자연스럽게 크기도 알 수 있게 되고, 알게 된 자식 요소의 크기만큼 main의 크기가 자동으로 설정된다.

 그러나 사실 overflow 속성은 float 이슈를 해결하기 위해 탄생된 속성은 아니다. 본래는 요소 크기보다 콘텐츠의 크기가 큰 경우에 콘텐츠를 어떻게 시각적으로 처리할 수 있을지에 대한 고민에서 탄생된 속성이다.

💡 overflow: visible

💡 overflow: hidden

💡 overflow: scroll

💡 overflow: auto

 위와 같이 MDN의 사용 예제를 보면 overflow 속성의 사용 목적을 시각적으로 체감할 수 있다. 이처럼 overflow 속성 역시 원래 목적과는 다르지만, 값을 hidden이나 scroll 등으로 설정했을 때 BFC가 생성된다는 특징을 이용하여 float 이슈를 해결하는 용도로 사용된다.

 다만 보편적으로 scroll보다는 hidden을 사용하는데, 그 이유는 위의 그림을 보면 알 수 있다. scroll로 지정하면 의도하지 않은 스크롤 UI가 생기기 때문이다. 그런데 위의 그림, 어딘가 익숙하다고 느껴지지 않는가? 아까 위에서 브라우저 창을 축소시켰던 예제의 그림과 유사하다는 생각이 든다.

 이를 통해 브라우저 창 밖으로 float된 요소가 튀어나오지 않는 매커니즘을 시각적으로 파악할 수는 없지만, 어렴풋이 overflow 속성을 scroll 값으로 지정하는 것과 유사한 방식이지 않을까 추측해볼 수는 있겠다.


🔸 flow-root 이용하기

 앞서 살펴본 overflow 속성을 이용한 해결 방법 외에도, 부모 요소의 display 속성 값을 flow-root로 설정하여 해결하는 방법이 있다. MDN에서는 flow-root에 대해 다음과 같이 정의하고 있다.

💡 flow-root

: The element generates a block element box that establishes a new block formatting context, defining where the formatting root lies.

 이를 해석해보면, flow-root로 값을 설정한 요소는 BFC에 의해 동작하는 block 레벨 레이아웃으로 지정된다고 볼 수 있다.

 결국 flow-root를 사용하여 해결하는 방법 역시 overflow 속성을 사용하여 해결하는 방법과 같은 맥락이다. display의 값을 flow-root로 설정하면 BFC가 생성되기 때문에 자체적으로 float 값을 계산할 수 있는 block 레벨 레이아웃이 설정되는 것이다.

.main{
 background: rgb(243, 156, 240);
 display: flow-root;
}

 예제와 함께 살펴보면, 위의 그림은 main 요소의 display 값을 flow-root로 설정한 결과다. 역시 overflow 값을 hidden으로 설정했을 때와 마찬가지로 main 요소는 하위 요소인 div의 콘텐츠 크기만큼 height을 갖게 된다.


🔸 clear 이용하기

 float 이슈를 해결할 수 있는 방법이 한 가지 더 있다. 바로 부모 요소에 가상 요소 선택자로 block 요소를 추가하여 clear 속성을 both로 지정해주는 것이다. 구체적인 예제를 살펴보기 전에 clear 속성이 무엇인지 한 번 간단하게 알아보겠다.

💡 clear: none

💡 clear: left

💡 clear: right

💡 clear: both

 위의 MDN 예제를 보면 알 수 있듯이, clear는 쉽게 말해 float 기능을 해제하는 속성이라고 볼 수 있겠다. 구체적으로는 float와 연관된 block 요소에 clear를 적용하면, float가 적용된 요소의 최하단으로 테두리가 이동되면서 block 요소가 float된 요소의 좌표 값을 인식할 수 있게 된다.

 매커니즘의 이해가 좀 어렵지만, 결국 clear 역시 BFC와 유사한 면이 있다. BFC를 통해 float된 요소의 좌표 값을 알 수 있는 것처럼, clear 속성을 적용함으로써 float된 요소의 주변 block 요소가 float된 요소의 좌표 값을 알게 되는 것이다.

 .main::after{
  content: '';
  display: block;
  height: 5px;
  background: orange;
  clear: both;
}

 예제를 통해 살펴보면 위와 같다. 먼저 main 요소에 ::after로 가상의 block 요소를 하나 추가한다. 시각적인 이해를 돕기 위해 height 값과 background 값을 임시로 설정했다. 생성한 가상의 block 요소에 clear 속성 값을 both로 설정하면 floatmain의 하위 요소 테두리가 가상 요소 위치까지 내려오게 된다. float된 요소들의 좌표 값을 알게 되었으므로 main 요소는 자연스럽게 하위 요소의 크기만큼 height 값이 자동으로 적용된다.

.main::after{
 content: '';
 display: block;
 clear: both;
}

 시각적인 이해를 돕기 위해 설정했던 속성을 다 제외하고 나면, overflowflow-root를 사용한 방법과 동일하게 정상 동작하는 것을 확인할 수 있다.


🔸 그 외..?

 사실 위에서 소개한 방법 외에도 부모 요소의 height 값을 애초에 지정하거나, display 값을 inline-block으로 설정하는 방법도 있다. 하지만 레이아웃을 수정해야 하는 상황에서 유연하게 대처하기 힘들다는 단점이 있기 때문에 웬만하면 위의 세 가지 방법을 상황에 맞게 적절히 사용하는 것이 바람직하다.


😅 어렵지만 재미있는 float

 사실 float 이슈가 발생하면 기계적으로 overflow: hidden을 쓰라는 말을 계속 들어왔었다. 그런데 이렇게 구체적인 원리를 공부하고자 하는 마음이 들었던 이유는, 지속 가능성에 있다. 원리를 이해하지 못한 상태에서 기계적으로 코드를 작성하다 보니까 조금만 상황이 변동되더라도 원점에서 헤매게 되는 경우가 비일비재했기 때문이다.

 float 이슈를 이해하는 것은 어렵다. 작동되는 매커니즘이 정확히 눈으로 보이지 않기도 하고, BFC와 같은 추상적인 개념의 선행적 이해가 필요하기 때문이다. 이 글을 마무리하는 지금도 한 8~90%는 이해가 되었다고 생각하지만, 아직 아리송한 부분이 좀 있다. 이 부분은 실제로 코드를 작성해보면서 앞으로 채워나가야 할 것 같다.

🙏 출처

https://developer.mozilla.org/ko/docs/Web/CSS/float
https://developer.mozilla.org/ko/docs/Web/Guide/CSS/Block_formatting_context
https://developer.mozilla.org/ko/docs/Web/CSS/overflow
https://developer.mozilla.org/ko/docs/Web/CSS/display
https://developer.mozilla.org/en-US/docs/Web/CSS/clear

profile
𝙸'𝚖 𝚊 𝚍𝚎𝚟𝚎𝚕𝚘𝚙𝚎𝚛 𝚝𝚛𝚢𝚒𝚗𝚐 𝚝𝚘 𝚜𝚝𝚞𝚍𝚢 𝚊𝚕𝚠𝚊𝚢𝚜. 🤔

1개의 댓글

comment-user-thumbnail
2023년 10월 8일

BFC 관련 글 중 가장 이해가 잘 되었습니다! 좋은 글 감사합니다.

답글 달기