박스 트리
고친 사람 github-actions[bot]
박스 트리는 웹 문서의 요소들을 화면에 놓을 사각형 상자로 바꿔 줍니다. 이 사각형 상자를 박스라고 부릅니다. 브라우저는 박스마다 크기와 위치를 정한 다음 화면에 칠합니다. 요소 하나가 박스를 안 만들기도 하고 여럿 만들기도 해서 박스 트리의 모양은 문서의 구조와 조금 다릅니다.
쉽고 빠른 이해
박스 트리는 웹 문서를 화면에 놓을 박스들로 바꿔 줍니다. 문단 하나는 박스 하나가 됩니다. 숨긴 문단은 박스를 만들지 않습니다. 목록의 항목 하나는 글머리 기호 몫까지 박스 둘을 만듭니다.
요소와 박스는 하나씩 짝이 맞지 않습니다. 박스만 모은 트리를 먼저 세워 두면 크기와 위치를 정하는 단계는 요소 사정을 따지지 않고 박스만 봅니다.
- 요소마다 스타일 규칙을 적용해 속성의 최종 값을 정합니다
- 요소마다
display값을 보고 박스를 몇 개 만들지 정합니다 - 요소에 안 싸인 글자는 이름 없는 박스로 감쌉니다
- 다음 단계가 박스마다 크기와 위치를 정하고 칠합니다
대가도 있습니다. 요소를 숨기거나 드러낼 때마다 트리를 다시 세웁니다. 이름 없는 박스는 스타일 규칙으로 직접 꾸밀 수 없습니다.
평소에는 박스 트리를 의식할 일이 거의 없습니다. 배치가 예상과 어긋날 때 떠올리면 원인이 보입니다.
상세
이삿짐센터는 집 안 물건 목록을 받아 상자에 나눠 담습니다. 버리기로 한 물건은 상자에 안 들어갑니다. 가구에 딸린 부속품은 본체 상자 옆에 작은 상자 하나를 더 받습니다. 서랍에서 굴러다니던 자잘한 물건은 이름표 없는 상자 하나에 모읍니다.
트럭에 실을 때 일꾼은 물건 목록이 아니라 상자만 보고 어디에 놓을지 정합니다. 박스 트리가 이 상자 더미입니다. 물건 목록은 웹 문서의 구조입니다. 트럭에 싣는 일은 크기와 위치를 정하는 단계입니다.
이 절은 짧은 웹 문서 조각 몇 개를 박스 트리로 바꿔 보며 따라갑니다. 요소 하나가 박스를 몇 개 만드는지, 요소 없이 생기는 박스는 무엇인지, 박스 트리를 받은 다음 단계가 무엇을 하는지가 차례로 나옵니다.
원본 트리와 박스
HTML(HyperText Markup Language)은 웹 문서의 내용과 구조를 적는 언어입니다. <p> 같은 태그로 문서를 조각냅니다. 태그로 감싼 조각 하나가 요소입니다.
브라우저는 HTML 을 읽어 요소들을 부모와 자식으로 잇습니다. 이렇게 이어진 트리를 원본 트리라고 합니다. 박스 트리를 만드는 재료입니다.
웹 페이지에서는 DOM(Document Object Model, 문서 객체 모델)이 원본 트리입니다. 자바스크립트가 문서를 읽고 고칠 때 만지는 것도 이 트리입니다.
CSS(Cascading Style Sheets)는 요소의 생김새를 적는 언어입니다. 브라우저는 요소마다 걸리는 CSS 규칙을 모아 속성마다 최종 값을 하나씩 정합니다. 이 값의 묶음을 계산된 스타일이라고 부릅니다. 박스 트리는 원본 트리와 계산된 스타일을 함께 읽어 만듭니다.
박스는 화면에서 사각형 하나를 차지할 단위입니다. 크기와 위치를 정하는 단계는 요소가 아니라 박스마다 폭과 높이와 위치를 매깁니다. 박스가 있어야 무엇을 어디에 놓을지 계산할 수 있습니다.
박스 하나는 네 겹으로 되어 있습니다. 가운데에 글자나 자식 박스가 담기는 내용 영역이 있습니다. 그 바깥을 안쪽 여백, 테두리, 바깥 여백이 차례로 둘러쌉니다. 이 네 겹 구조가 박스 모델입니다.
박스 트리는 이 박스들을 부모와 자식으로 이은 트리입니다. 자식 박스는 부모 박스 안에 놓입니다. 원본 트리의 부모와 자식 관계를 대개 따라가지만 하나씩 짝이 맞지는 않습니다. 아래 소절들이 짝이 어긋나는 경우를 하나씩 다룹니다.
블록 박스와 인라인 박스
박스는 부모 안에서 놓이는 방식에 따라 두 가지로 나뉩니다. 블록 박스는 앞뒤로 줄을 바꾸며 위에서 아래로 쌓입니다. 문단이나 제목이 만드는 박스가 이쪽입니다.
인라인 박스는 글자처럼 한 줄 안에서 옆으로 이어집니다. 문장 안의 굵은 글씨나 링크가 만드는 박스가 이쪽입니다. 줄이 차면 다음 줄로 넘어갑니다.
어느 쪽이 될지는 display 속성이 정합니다. display: block 은 블록 박스를, display: inline 은 인라인 박스를 만듭니다.
브라우저가 요소마다 미리 정해 둔 값이 있습니다. <p> 는 블록으로, <span> 은 인라인으로 시작합니다. 미리 정해 둔 값의 묶음이 기본 스타일시트입니다. 그래서 CSS 를 한 줄도 안 써도 문단은 위아래로 쌓입니다.
요소 하나가 만드는 박스의 수
요소와 박스가 어떻게 짝을 이루는지 짧은 문서 하나로 봅니다. 줄마다 오른쪽 주석이 그 요소가 만드는 박스의 수를 적었습니다.
<div> <!-- 박스 1 -->
<p>배송 중</p> <!-- 박스 1 -->
<p class="memo">메모</p> <!-- 박스 0 -->
<ul> <!-- 박스 1 -->
<li>사과</li> <!-- 박스 2 -->
</ul>
</div>
이 문서에는 CSS 한 줄이 걸려 있습니다. class 가 memo 인 요소를 display: none 으로 둡니다.
.memo { display: none; }
display: none 은 「이 요소에는 박스를 만들지 말라」는 값입니다. .memo 문단은 박스를 하나도 만들지 않습니다. 그 아래 자식 요소도 함께 박스를 잃습니다. 화면에서 이 문단이 차지할 공간도 사라집니다.
<li> 는 목록의 항목 하나입니다. 브라우저는 항목 앞에 글머리 기호를 그립니다. 이 기호는 HTML 에 적힌 적이 없습니다. 그래도 마커 박스라는 박스를 따로 받습니다.
그래서 <li> 하나가 박스 둘을 만듭니다. 항목 본문의 박스와 마커 박스입니다.
아래는 이 문서의 박스 트리입니다. 요소 안의 글자는 따로 칸을 두지 않고 그 박스 칸 안에 적었습니다.
flowchart TD
D["div 블록 박스"] --> P["p 블록 박스 · 글자 배송 중"]
D --> U["ul 블록 박스"]
U --> L["li 블록 박스 · 글자 사과"]
L --> M["마커 박스 · 글머리 기호"]
요소 다섯이 박스 다섯을 만들었습니다. 수는 같지만 구성이 다릅니다. .memo 문단의 박스가 빠졌습니다. 원본 트리에 없던 마커 박스는 새로 생겼습니다.
다음 단계가 이런 사정을 요소마다 따로 따지면 계산이 갈래투성이가 됩니다. 박스 트리를 한 번 세워 두면 그 단계는 요소 사정을 따지지 않고 박스만 봅니다. 박스 트리가 따로 있는 까닭입니다.
CSS 가 박스를 더 만들게 할 수도 있습니다. p::before { content: "▶"; } 를 걸면 문단마다 내용 앞에 「▶」 글자가 붙습니다. 이 글자는 HTML 에는 없지만 박스 트리에는 박스로 들어갑니다.
이렇게 HTML 에 없이 CSS 가 만든 조각을 의사 요소라고 부릅니다. ::before 는 요소의 내용 앞에, ::after 는 내용 뒤에 조각을 덧붙입니다.
요소마다 만드는 박스의 수를 모으면 아래 표가 됩니다.
| 요소 | 만드는 박스 |
|---|---|
display: none 이 걸린 요소 |
없음. 자식도 박스를 안 만든다 |
| 문단 · 제목 같은 보통 요소 | 하나 |
| 목록 항목 | 둘. 본문 박스와 마커 박스 |
::before · ::after 가 걸린 요소 |
자기 박스에 의사 요소 박스가 더해진다 |
익명 박스
앞에서는 요소가 박스를 잃거나 더 얻었습니다. 이번에는 반대로 요소 없이 박스가 생기는 경우입니다.
<div> <!-- 블록 박스 -->
주문 번호 1042 <!-- 요소 없는 글자 -->
<p>배송 중</p> <!-- 블록 박스 -->
</div>
<div> 안에 요소에 안 싸인 글자와 문단이 함께 있습니다. 요소에 안 싸인 글자는 한 줄 안에서 흐르므로 인라인으로 칩니다. 문단은 블록입니다. 그래서 <div> 의 자식에 인라인과 블록이 섞였습니다.
블록 박스의 자식은 모두 블록이거나 모두 인라인이어야 합니다. 그래야 위에서 아래로 쌓을지 한 줄로 이을지가 하나로 정해집니다.
그래서 브라우저는 「주문 번호 1042」를 블록 박스 하나로 감쌉니다. 요소 없이 생긴 이 박스를 익명 박스라고 부릅니다. 이제 <div> 박스의 자식은 익명 블록 박스와 <p> 블록 박스, 둘 다 블록입니다.
CSS 규칙은 선택자로 걸릴 요소를 고릅니다. p 나 .memo 처럼 요소의 이름이나 class 를 적는 부분이 선택자입니다. 익명 박스는 짝이 되는 요소가 없어서 선택자로 고를 수 없습니다.
익명 박스의 생김새는 부모 박스를 따라갑니다. 글자색이나 글꼴처럼 자식에게 물려주는 속성은 부모에게서 받습니다. 이렇게 물려받는 규칙이 CSS 상속입니다. 테두리나 여백처럼 물려주지 않는 속성은 CSS 가 정해 둔 처음 값으로 남습니다.
「주문 번호 1042」에 테두리를 주고 싶으면 이 글자를 <p> 같은 요소로 감쌉니다. 그러면 익명 박스 대신 선택자로 고를 수 있는 박스가 생깁니다.
박스 트리 다음에 오는 단계
박스 트리는 무엇을 그릴지까지만 정합니다. 박스마다 크기와 위치는 아직 없습니다. 박스 트리를 받은 뒤에는 두 단계가 이어집니다.
브라우저는 박스 트리를 뿌리부터 훑으며 박스마다 폭과 높이와 위치를 정합니다. 이 단계가 레이아웃입니다. 부모 박스의 폭이 정해져야 자식 박스의 폭을 정할 수 있습니다. 계산은 위에서 아래로 내려갑니다.
레이아웃에서 박스 하나가 여러 조각으로 나뉘기도 합니다. 인라인 박스의 글자가 한 줄에 다 안 들어가면 줄마다 조각 하나씩으로 쪼개집니다. 인쇄할 때 블록 박스가 두 쪽에 걸치면 쪽마다 조각이 생깁니다. 레이아웃 결과를 이 조각 단위로 모은 트리를 조각 트리라고 부릅니다.
조각마다 위치가 정해지면 색과 글자와 테두리를 칠합니다. 이 단계가 페인트입니다.
칠하는 바탕은 캔버스입니다. 캔버스는 문서 전체를 받는 끝없는 평면입니다. 브라우저 창은 그 평면의 한 부분을 비춰 보여 줍니다.
지금까지의 단계를 이으면 아래 그림입니다.
flowchart TD
A["원본 트리 · 요소와 글자"] --> B["계산된 스타일"]
A --> C["박스 트리 · 그릴 박스"]
B --> C
C --> D["레이아웃 · 크기와 위치"]
D --> E["조각 트리 · 줄과 쪽마다 나뉜 조각"]
E --> F["페인트 · 캔버스에 칠하기"]
트리가 두 번 모양을 바꿉니다. 요소의 트리가 박스의 트리가 됩니다. 박스의 트리는 다시 조각의 트리가 됩니다. 단위가 바뀔 때마다 앞 트리의 노드 하나가 뒤 트리에서 없어지거나 여럿으로 늘어날 수 있습니다.
박스 트리를 바꾸는 display 값
개발자가 박스 트리를 직접 만들지는 않습니다. display 값을 골라 박스 트리의 모양을 바꿀 뿐입니다. 이 소절은 그 효과가 잘 보이는 값 하나와 박스 트리를 떠올려야 할 때를 다룹니다.
display: flex 를 준 박스는 자식 박스들을 한 줄에 옆으로 늘어놓습니다. 이 배치 방식을 플렉스박스라고 부릅니다.
늘어놓는 대상은 자식 요소가 아니라 자식 박스입니다. 이렇게 늘어놓이는 자식 박스 하나하나가 항목입니다.
아래는 입력 칸과 버튼을 플렉스박스의 항목으로 나란히 놓으려는 코드입니다. 둘 사이에 <form> 요소가 끼어 있습니다.
<div class="bar"> <!-- 플렉스 -->
<form class="wrap"> <!-- 박스 없음 -->
<input> <!-- 항목 -->
<button>검색</button> <!-- 항목 -->
</form>
</div>
CSS 는 두 줄입니다. 첫째 줄은 .bar 에 플렉스박스를 겁니다. 둘째 줄은 폼에 display: contents 라는 값을 줍니다.
.bar { display: flex; }
.wrap { display: contents; }
둘째 줄이 없으면 .bar 박스의 자식은 <form> 박스 하나뿐입니다. 플렉스박스가 늘어놓는 항목도 폼 하나입니다.
입력 칸과 버튼은 폼 박스 안에서 인라인 박스처럼 글자 줄에 옆으로 놓입니다. 둘은 .bar 의 항목이 아니라서 플렉스박스의 배치 규칙을 받지 않습니다. 입력 칸이 남은 폭을 채우게 하는 것 같은 항목 설정이 둘에게는 닿지 않습니다.
display: contents 는 요소 자신의 박스만 빼고 자식들의 박스는 남깁니다. 폼의 박스가 빠지자 입력 칸과 버튼이 .bar 박스의 자식이 됩니다. 이제 둘이 플렉스박스의 항목으로 한 줄에 나란히 섭니다.
폼 요소는 원본 트리에 남아 있습니다. 그래서 버튼을 누르면 폼 제출은 여전히 됩니다. 원본 트리는 손대지 않고 박스 트리의 한 층만 걷어 낸 것입니다.
평소에는 박스 트리를 의식할 일이 거의 없습니다. 배치가 예상과 어긋날 때 떠올리면 원인이 보입니다. 브라우저 개발자 도구의 요소 패널이 펼쳐 보이는 트리는 원본 트리입니다. 익명 박스는 거기에 나오지 않습니다.
display 를 바꾸는 데는 비용이 듭니다. 박스가 생기거나 빠지니 브라우저가 박스 트리를 다시 세웁니다. 둘레 박스의 크기와 위치도 다시 정해야 해서 레이아웃이 다시 돕니다. 레이아웃을 다시 도는 이 일이 리플로입니다.
렌더 트리와 박스 트리
박스 트리는 CSS 규격 문서가 쓰는 이름입니다. 브라우저 안에서 화면을 그리는 부품인 렌더링 엔진은 이 구조를 저마다 다른 이름으로 들고 있습니다. 렌더 트리 · 레이아웃 트리 · 프레임 트리가 그런 이름입니다.
박스 트리는 어떤 박스가 있어야 하는지를 정한 결과입니다. 엔진이 이 결과를 어떤 객체로 담을지는 엔진마다 다릅니다. 엔진의 트리와 박스 트리가 노드 하나하나까지 같다고 볼 수는 없습니다.
렌더 트리는 흔히 화면에 보일 요소만 골라 둔 트리로 설명됩니다. 박스 트리로 보면 고른 단위가 요소가 아니라 박스입니다. 그래서 요소가 없는 익명 박스도, HTML 에 없는 마커 박스도 이 트리에 들어갑니다.
관련 항목
박스 트리를 만드는 재료
원본 트리 · DOM · HTML · CSS · CSSOM · 계산된 스타일 · 사용자 에이전트 스타일시트
박스 트리를 이루는 구성 요소
박스 모델 · 블록 박스 · 인라인 박스 · 익명 박스 · 마커 박스 · 의사 요소 · 텍스트 런
박스를 몇 개 만들지 가르는 CSS 속성
display · visibility · content · list-style
계산된 스타일을 정하는 규칙
CSS 선택자 · CSS 상속 · 캐스케이드 · 명시도
박스 트리의 박스를 늘어놓는 배치 방식
일반 흐름 · 블록 서식 맥락 · 인라인 서식 맥락 · 플렉스박스 · CSS 그리드 · 표 레이아웃
박스 트리 뒤에 이어지는 처리 단계
레이아웃 · 프래그먼트 트리 · 줄 박스 · 페인트 · 합성 · 캔버스 · 리플로
박스 트리를 가리키는 다른 이름
렌더 트리 · 레이아웃 트리 · 프레임 트리
박스 트리가 속하는 상위 분류
렌더링 · 렌더링 엔진 · 브라우저 · 중요 렌더링 경로 · 시각 서식 모델
박스 트리와 헷갈리는 이웃
접근성 트리 · 섀도 DOM · 가상 DOM · 바운딩 박스
다른 이름: box tree