렌더 트리
고친 사람 github-actions[bot]
렌더 트리는 브라우저가 화면에 그릴 요소만 골라 둔 트리입니다. 요소마다 입을 스타일도 함께 붙여 둡니다. 웹 문서의 구조와 스타일 규칙을 합쳐서 만듭니다. 브라우저는 이것을 보고 요소마다 크기와 위치를 정한 뒤 화면에 칠합니다. 숨기기로 한 요소는 여기에 들어오지 않습니다.
쉽고 빠른 이해
렌더 트리는 화면에 나올 요소와 그 생김새만 적은 명단입니다. 웹 문서에 문단이 다섯 개 있습니다. 스타일 규칙은 그중 하나를 숨기라고 합니다. 그러면 명단에는 문단 넷만 오릅니다. 문단마다 글자색과 글자 크기가 함께 적힙니다.
웹 문서의 구조에는 화면에 안 나오는 것까지 다 들어 있습니다. 스타일 규칙은 규칙별로 적혀 있어서 요소 하나가 어떻게 보일지 바로 알 수 없습니다. 둘을 한 번 합쳐 두어야 다음 단계가 화면에 나올 것만 훑습니다. 그 단계가 크기와 위치를 정합니다.
- 웹 문서를 읽어 구조를 트리로 만듭니다. 스타일 규칙도 따로 읽어 둡니다
- 요소마다 걸리는 규칙을 찾아 최종 생김새를 정합니다. 숨긴 요소는 뺍니다
- 남은 요소로 트리를 세워 넘깁니다. 다음 단계가 크기와 위치를 정하고 화면에 칠합니다
대가는 기다림과 되풀이입니다. 스타일 규칙을 다 받기 전에는 이 명단을 세우지 못해 첫 화면이 늦어집니다. 요소를 숨기거나 드러낼 때마다 명단을 고칩니다. 크기와 위치도 다시 계산합니다.
상세
연극 극단에는 단원 전체를 적은 명부가 있습니다. 배역마다 입을 옷을 정한 의상표도 따로 있습니다. 공연 날 무대 감독은 둘을 맞대어 오늘 무대에 오를 배우와 그 배우가 입을 옷만 적은 출연표를 만듭니다. 출연표에는 무대 어디에 설지가 아직 적히지 않습니다.
렌더 트리가 이 출연표입니다. 단원 명부는 웹 문서의 구조이고 의상표는 스타일 규칙입니다. 이 절은 세 가지를 따라갑니다. 두 재료가 무엇인지, 합칠 때 무엇이 들어가고 빠지는지, 렌더 트리가 아직 모르는 것이 무엇인지입니다.
재료가 되는 두 트리
HTML(HyperText Markup Language)은 웹 문서의 내용과 구조를 적는 언어입니다. <p> 같은 태그로
문서를 조각냅니다. 태그 하나로 적힌 조각을 요소라고 부릅니다.
브라우저는 HTML 을 읽어 요소를 부모와 자식으로 잇습니다. <body> 안에 <p> 가 있으면 <p> 는
<body> 의 자식입니다. 이렇게 이어진 트리가 DOM(Document Object Model, 문서 객체
모델)입니다.
트리를 이루는 칸 하나하나가 노드입니다. DOM 에는 문서에 적힌 것이 전부 노드로 들어갑니다. 브라우저 탭 제목에 쓰일 <title> 도, 실행할 코드를 담은
<script> 도 노드가 됩니다. 이런 요소는 화면 본문에 그려지지 않습니다.
CSS(Cascading Style Sheets)는 요소의 생김새를 적는 언어입니다. p { color: gray; } 는 「모든
<p> 의 글자색을 회색으로」라는 규칙 하나입니다. 중괄호 앞은 규칙이 걸릴 요소를, 중괄호 안은 매길
값을 적습니다.
브라우저는 CSS 도 읽어 객체의 트리인 CSSOM(CSS Object Model, CSS 객체 모델)으로 옮겨 둡니다. 뿌리는 스타일시트 하나입니다. 그 아래에 규칙이 하나씩 자식으로 달립니다. 규칙 밑에는 걸릴 요소와 매길 값이 다시 달립니다.
CSSOM 의 가지는 요소가 아니라 규칙을 따라 뻗습니다. 「이 <p> 하나는 무슨 색인가」를 물으면 CSSOM 만으로는 답이
바로 안 나옵니다. 그 요소에 걸리는 규칙을 전부 찾아 견줘야 합니다.
두 트리는 각자 절반씩만 압니다. DOM 은 무엇이 있는지 알지만 어떻게 보일지는 모릅니다. CSSOM 은 규칙을 알지만 요소 하나하나에 값을 매겨 두지 않았습니다. 렌더 트리는 이 둘을 합쳐 「무엇이 있고 어떻게 보이나」를 노드 하나에 모읍니다.
두 트리를 합치는 스타일 계산
합치는 일은 한 단계가 맡습니다. 그 단계가 요소마다 정하는 것은 속성의 최종 값입니다.
브라우저는 DOM 의 뿌리부터 노드를 하나씩 훑습니다. 노드마다 CSSOM 에서 그 요소에 걸리는 규칙을 모읍니다. 이 단계를 스타일 계산이라고 부릅니다.
한 요소에 규칙 여럿이 걸리면 서로 부딪힐 수 있습니다. 한 규칙은 글자를 회색으로, 다른 규칙은 빨강으로 칠하라고 하는 식입니다. 어느 쪽이 이길지 가르는 규칙을 캐스케이드라고 합니다.
다툼을 다 풀고 나면 요소마다 속성의 최종 값이 하나씩 남습니다. 글자색은 빨강, 글자 크기는 16px 같은 값입니다. 요소에 매겨진 이 값의 묶음이 계산된 스타일입니다.
렌더 트리의 노드는 요소 하나와 그 요소의 계산된 스타일을 짝지어 가집니다. 다음 단계는 규칙을 다시 뒤지지 않습니다. 노드에 붙은 값만 읽습니다.
렌더 트리에 오르는 노드와 빠지는 노드
DOM 의 노드가 전부 렌더 트리에 오르지는 않습니다. 아래 짧은 문서 하나로 무엇이 남고 무엇이 빠지는지 가려 봅니다. 줄마다 오른쪽 주석이 그 요소가 렌더 트리에 남는지를 적었습니다.
<body> <!-- 남음 -->
<h1>주문 내역</h1> <!-- 남음 -->
<p class="tip">쿠폰</p> <!-- 남음 -->
<p class="memo">메모</p> <!-- 빠짐 -->
<p class="ghost">빈칸</p> <!-- 남음 -->
<script>...</script> <!-- 빠짐 -->
</body>
요소 여섯 가운데 둘이 빠집니다. 무엇이 그렇게 가르는지는 이 문서에 걸린 CSS 세 줄에 있습니다.
.memo { display: none; }
.ghost { visibility: hidden; }
.tip::before { content: "안내 "; }
.memo 처럼 점으로 시작하는 부분은 class 가 memo 인 요소를 가리킵니다. 그래서 첫 줄은
<p class="memo"> 에, 둘째 줄은 <p class="ghost"> 에 걸립니다.
display: none 은 「이 요소를 그리지도 말고 공간도 주지 말라」는 선언입니다. 이 선언이 걸린 요소는
렌더 트리에 오르지 않습니다. 그 아래 매달린 자식들도 함께 빠집니다. .memo 문단이 이렇게 빠졌습니다.
이 CSS 에는 <script> 에 걸리는 규칙이 없습니다. 그래도 <script> 는 빠집니다. 브라우저는 모든
문서에 자기가 가진 CSS 규칙 묶음을 먼저 깔아 둡니다. 이 묶음을 기본 스타일시트라고 부릅니다.
기본 스타일시트는 <head> · <title> · <script> 같은 요소를 display: none 으로 둡니다. 그래서
화면에 안 나오는 요소가 빠지는 까닭도 결국 display: none 하나입니다.
visibility: hidden 은 다르게 동작합니다. 요소를 안 보이게 하지만 그 요소가 차지할 공간은 남깁니다.
공간을 남기려면 크기를 재야 하므로 이 요소는 렌더 트리에 오릅니다. 화면에는 그 크기만큼 빈칸이
생깁니다.
셋째 줄의 ::before 는 요소 앞에 글자를 덧붙이는 의사 요소입니다. HTML 에는 없고 CSS 가 만들어
낸 조각입니다. 그래서 DOM 에 없는 노드가 렌더 트리에는 생깁니다.
::before 노드는 .tip 노드 아래 첫 자식으로 붙습니다. 첫 자식이라 .tip 문단의 글자보다 앞에
「안내」가 그려집니다.
아래는 이 문서의 렌더 트리입니다. 남은 노드만 그렸습니다.
flowchart TD
B["body"] --> H["h1 · 주문 내역"]
B --> T[".tip 문단"]
B --> G[".ghost 문단 · 빈칸만 남김"]
T --> BF["::before · 안내"]
DOM 의 노드 여섯 가운데 넷이 남았습니다. 거기에 DOM 에 없던 ::before 노드가 하나 더해졌습니다.
DOM 노드와 렌더 트리 노드는 하나씩 짝이 맞지 않습니다. 렌더 트리를 DOM 의 사본으로 여기면 이 차이를
놓칩니다.
렌더 트리가 아직 모르는 크기와 위치
렌더 트리는 화면에 나올 것과 그 생김새까지 압니다. 렌더 트리 다음에는 두 단계가 더 붙습니다.
렌더 트리의 노드에는 아직 크기와 위치가 없습니다. 계산된 스타일에 width: 50% 가 적혀 있어도
부모의 폭을 모르면 실제 폭을 정할 수 없습니다. 부모의 폭은 다시 브라우저 창의 폭에 달려 있습니다. 창에서 문서가 보이는 영역이 뷰포트입니다.
브라우저는 뷰포트 크기에서 출발해 렌더 트리를 뿌리부터 훑습니다. 노드마다 크기와 위치를 정합니다. 이 단계가 레이아웃입니다.
크기와 위치가 정해지면 노드마다 색과 글자와 테두리를 화면의 점으로 칠합니다. 이 단계가 페인트입니다. 무슨 색으로 칠할지는 계산된 스타일에서, 어디를 칠할지는 레이아웃 결과에서 옵니다.
지금까지 본 단계를 이으면 아래 그림입니다.
flowchart TD
H["HTML"] --> D["DOM"]
C["CSS"] --> O["CSSOM"]
D --> S["스타일 계산"]
O --> S
S --> R["렌더 트리"]
R --> L["레이아웃 · 크기와 위치"]
L --> P["페인트 · 화면에 칠하기"]
HTML 과 CSS 는 따로 읽혀 두 트리가 됩니다. 두 갈래는 스타일 계산에서 처음 만납니다. 렌더 트리부터는 한 줄로 이어집니다.
화면이 바뀔 때 다시 도는 단계
페이지가 뜬 뒤에도 자바스크립트 코드가 DOM 이나 스타일을 바꿉니다. 무엇을 바꿨느냐에 따라 다시 도는 단계가 달라집니다.
요소를 더하거나 display 값을 바꾸면 렌더 트리의 모양이 바뀝니다. 노드가 생기거나 빠지니 둘레
노드의 크기와 위치도 다시 정해야 합니다. 이렇게 레이아웃을 다시 도는 일을 리플로라고 부릅니다.
글자색처럼 크기와 위치에 닿지 않는 값만 바꾸면 렌더 트리의 모양도 레이아웃도 그대로입니다. 노드에 붙은 스타일 값만 고칩니다. 그리고 다시 칠합니다. 이것이 리페인트입니다.
바꾼 것마다 다시 도는 단계를 모으면 아래 표가 됩니다.
| 바꾼 것 | 렌더 트리 | 레이아웃 | 페인트 |
|---|---|---|---|
요소 추가·삭제 · display 켜고 끄기 |
노드가 생기거나 빠짐 | 다시 | 다시 |
| 폭 · 글자 크기 | 노드의 값만 고침 | 다시 | 다시 |
visibility 켜고 끄기 |
노드의 값만 고침 | 그대로 | 다시 |
| 글자색 · 배경색 | 노드의 값만 고침 | 그대로 | 다시 |
레이아웃을 다시 도는 윗줄이 아랫줄보다 일이 많습니다. 한 노드의 크기가 바뀌면 형제와 부모의 위치도
따라 바뀌어 범위가 넓어지기 쉽습니다. 자주 숨겼다 드러내는 요소에 display 대신 visibility 를
고르기도 하는 까닭입니다.
첫 화면을 붙잡는 CSS
렌더 트리는 CSSOM 이 있어야 세울 수 있습니다. 이 조건이 페이지가 처음 뜨는 속도를 정합니다.
CSSOM 이 반쯤 만들어진 채로 렌더 트리를 세우면 뒤늦게 도착한 규칙이 앞의 계산을 뒤집을 수 있습니다. 그래서 브라우저는 CSS 파일을 다 받아 읽기 전까지 렌더 트리를 세우지 않습니다. 스타일 없는 화면이 잠깐 비쳤다가 바뀌는 일을 막으려는 동작입니다. CSS 가 이렇게 첫 화면을 붙잡는 성질을 렌더 블로킹이라고 부릅니다.
HTML 을 받은 뒤 첫 화면이 보일 때까지 밟는 단계를 한 줄로 이으면 중요 렌더링 경로가 됩니다. DOM · CSSOM · 렌더 트리 · 레이아웃 · 페인트가 그 단계입니다. 이 가운데 한 단계만 늦어도 첫 화면이 늦게 뜹니다. 그래서 첫 화면이 늦을 때는 이 경로에서 막힌 단계를 찾습니다.
HTML 을 내려주는 서버는 이 경로의 출발점을 쥐고 있습니다. 첫 화면에 필요한 CSS 를 작게 줄여 일찍 보내면 렌더 트리도 그만큼 일찍 섭니다. 첫 화면에 안 쓰이는 CSS 를 뒤로 미루는 것도 같은 까닭입니다.
부르는 이름과 헷갈리는 이름
렌더 트리는 브라우저에서 화면을 그리는 부품인 렌더링 엔진 안에 있습니다. 엔진마다 이 구조를 부르는 이름이 다릅니다. 레이아웃 트리나 프레임 트리라고 부르는 엔진도 있습니다.
CSS 규격 문서는 비슷한 구조를 박스 트리라고 부릅니다. 요소 하나가 화면에서 차지하는 사각형을 박스라고 합니다. 박스 트리는 그 박스들을 부모와 자식으로 이은 트리입니다. 엔진이 이 구조를 어떤 객체로 들고 있을지는 엔진마다 다릅니다.
「렌더」가 들어간 이름이 많아 헷갈리기 쉽습니다. 서버 사이드 렌더링의 렌더는 서버가 HTML 을 완성해 내려주는 일을 말합니다. 서버가 HTML 을 다 채워 보내도 렌더 트리는 여전히 브라우저가 만듭니다.
가상 DOM도 렌더 트리와 다릅니다. 자바스크립트 라이브러리가 메모리에 따로 들고 있는 DOM 의 사본입니다. 라이브러리는 고치기 전과 고친 뒤의 사본을 견줘 실제 DOM 에서 바꿀 곳만 찾습니다. 이 사본은 계산된 스타일도 크기도 갖지 않습니다.
관련 항목
렌더 트리를 만드는 재료
DOM · CSSOM · HTML · CSS · 파서 · 사용자 에이전트 스타일시트
렌더 트리를 만들 때 거치는 처리 단계
스타일 계산 · 캐스케이드 · 명시도 · CSS 상속 · 계산된 스타일
렌더 트리 뒤에 이어지는 처리 단계
레이아웃 · 리플로 · 페인트 · 리페인트 · 래스터화 · 합성
렌더 트리에 오를지를 가르는 CSS 속성
display · visibility · 의사 요소 · content-visibility
렌더 트리를 가리키는 다른 이름
레이아웃 트리 · 프레임 트리 · 박스 트리
렌더 트리가 속하는 상위 분류
렌더링 엔진 · 브라우저 · 렌더링 · 중요 렌더링 경로
렌더 트리를 늦추거나 되풀이시키는 원인
렌더 블로킹 · 레이아웃 스래싱 · 강제 동기 레이아웃 · FOUC
렌더 트리가 늦게 서면 나빠지는 성능 지표
첫 콘텐츠 페인트 · 첫 유의미 페인트 · 최대 콘텐츠 페인트 · 레이아웃 이동
렌더 트리와 이름이 겹치는 개념
가상 DOM · 서버 사이드 렌더링 · 렌더 파이프라인 · 접근성 트리 · 섀도 DOM
다른 이름: render tree · 렌더링 트리