1. Rendering Pipeline
사용자가 브라우저를 통해 웹 페이지에 접속하면, 가장 먼저 서버로부터 HTML
문서를 받아옵니다.
이후 이 HTML 문서를 해석(parsing
)해서 웹 페이지의 구조를 구성합니다. 이후 스타일과 스크립트를 읽고 반영하여 최종적으로 화면에 표시합니다.
이 전체 과정을 Rendering Pipeline
이라고 부릅니다.
브라우저의 Rendering Pipeline은 다음과 같은 순서로 수행됩니다.
- HTML 파싱 및 DOM 트리 생성
- CSS 파싱 및 CSSOM 트리 생성
- 렌더(Render) 트리 생성
- 레이아웃(Layout) 계산
- 페인팅(Painting)
- 컴포지팅(Compositing)
아래에서는 브라우저가 수행하는 작업에 대해 각 단계별로 상세하게 설명합니다.
2. HTML 파싱 및 DOM 트리 생성
파싱(Parsing
)이란 무엇일까요? 브라우저는 서버로부터 받은 HTML 문서를 문자열 형태로 받아옵니다. 이 문자열을 한 줄씩 읽으며, HTML 태그와 내용을 분석하여 DOM 트리(DOM Tree
) 를 생성합니다.
이때, 문자열 한 줄씩 읽는 과정을 파싱 과정이라 할 수 있습니다.
DOM은 문서 객체 모델(Document Object Model
)의 줄임말로, HTML을 트리 구조로 구성하여 객체로 만든 것을 말합니다. 예를 들어보겠습니다.
<!DOCTYPE html>
<html>
<head>
<title>운동 블로그</title>
</head>
<body>
<h1>헬스 일지</h1>
<p>오늘은 데드리프트를 5세트를 했다.</p>
</body>
</html>
위 코드를 DOM 트리로 구성하면 다음과 같이 됩니다.
html
├── head
│ └── title
└── body
├── h1
└── p
2.1. HTML 파싱을 멈추고 스크립트를 먼저 처리
브라우저는 HTML 문서를 위에서 아래로 동기적으로 파싱합니다. 그런데, 중간에 <script>
태그를 만나면 브라우저는 HTML 파싱을 잠시 멈추고 해당 JavaScript 코드를 먼저 실행합니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<title>HTML 파싱과 Script</title>
</head>
<body>
<p>1. 이 문장은 먼저 렌더링됩니다.</p>
<script>
// HTML 파싱이 여기서 멈추고 JavaScript를 먼저 실행합니다.
alert('2. JavaScript 실행!');
</script>
<p>3. 이 문장은 스크립트 실행 이후에 렌더링됩니다.</p>
</body>
</html>
<p>1. 이 문장은 먼저 렌더링됩니다.</p>
: 브라우저가 위에서 아래로 파싱 및 렌더링 합니다.<script>alert('2. JavaScript 실행!')</script>
: HTML 파싱을 멈추고 JavaScript 코드를 실행합니다.<p>3. 이 문장은 스크립트 실행 이후에 렌더링됩니다.</p>
: 사용자가 alert을 닫은 뒤 화면에 나타납니다.
2.2. defer와 async
기본적으로는 <script>
를 만나면, html 파싱을 중단하고 스크립트를 실행하지만, defer
와 async
속성을 사용하면 다르게 동작하도록 할 수 있습니다.
2.2.1. defer 속성
<script src="script.js" defer></script>
defer
속성을 사용하면, HTML 파싱 완전히 끝난 후 스크립트를 실행합니다. 하지만 스크립트 다운로드는 HTML 파싱과 동시에 진행됩니다. (스크립트 실행만 HTML 파싱 끝난 후 수행)
만약, defer 스크립트가 여러개인 경우, 위에서부터 아래오 순차적으로 수행합니다.
장점으로는 DOM이 완성된 뒤 실행되므로 요소를 못찾는 경우는 없어서 DOM 조작에 유리합니다. 또한, 스크립트로 인한 렌더링 지연이 없습니다.
2.2.2. async 속성
<script src="script.js" async></script>
async
속성은 defer와 동일하게 파싱과 다운로드를 동시에 수행합니다. 하지만, defer와 다르게 스크립트의 다운로드가 완료되면 그 즉시 스크립트를 실행합니다.
따라서, 스크립트 실행으로 인해, HTML 파싱이 중단될 수도 있습니다. 또한 async 스크립트가 여러 개인 경우, 먼저 다운로드된 순서대로 실행되므로 순서가 보장되지 않습니다.
3. CSS 파싱 및 CSSOM 트리 생성
HTML 파싱과 별도로 브라우저는 CSS 스타일도 파싱하여 CSSOM 트리(CSS Object Model Tree
) 를 만듭니다. 이 트리도 DOM 트리와 마찬가지로 스타일 정보를 트리 형태로 구성한 구조입니다.
CSS 파싱은 HTML 파싱과 병렬로 진행되기 때문에, 동시에 다운로드 및 파싱을 합니다. 하지만 style
태그 안에 있는 인라인 스타일이나 link
태그를 통해 불러온 외부 스타일이 완료되기 전에는 렌더링을 잠시 보류합니다.
4. 렌더 트리 생성
모든 파싱 작업이 끝나고 DOM 트리와 CSSOM 트리가 모두 완성되면, 브라우저는 이 2개를 조합하여 렌더 트리(Render Tree
)를 만듭니다.
렌더 트리는 실제로 화면에 보여지는 요소만 가지고 있습니다. 예를 들어
display: none
과 같이 보이지 않는 요소는 렌더 트리에 포함되지 않습니다.
렌더 트리의 각 노드들은 DOM Node + style
정보를 합친 객체입니다. 이 노드를 기준으로 위치와 크기를 계산합니다.
5. 레이아웃 계산
이제 렌더 트리를 기준으로 각 요소의 정확한 위치와 크기를 계산하는 작업을 시작합니다. 이 과정을 레이아웃(Layout
) 또는 리플로우(Reflow
)라고 합니다.
예를 들어, 다음과 같이 div 안에 여러 개의 글자가 들어 있는 경우, 글자들의 폰트 크기, 줄 간격, 여백(margin), 패딩(padding) 등을 고려해서 정확한 위치와 크기를 계산합니다.
<div style="width: 200px; padding: 10px;">
<p style="font-size: 16px;">운동을 합시다</p>
<p style="font-size: 14px;">집 안에만 있지말고, 집 밖으로 나갑시다!</p>
</div>
위 코드로 설명하면
- div의 너비를 200px로 계산
- padding을 고려해서 내부 p 요소의 위치를 조정
- p 요소는 폰트 크기에 따라 높이를 계산
6. 페인팅(Painting)
레이아웃 과정이 끝나면 브라우저는 실제 화면에 요소를 그리기 시작합니다. 이 단계를 페인팅(Painting
) 이라고 합니다. 이 과정에서는 텍스트, 색상, 그림자, 테두리 등 모든 시각적 요소가 픽셀 단위로 계산됩니다.
페인팅은 요소의 스타일을 기준으로 여러 개의 레이어를 만들어내며, 각 레이어를 나중에 합성(compositing
)하게 됩니다.
예를 들어, box-shadow
, border-radius
, transform
등의 스타일이 적용된 요소는 별도의 레이어로 나뉘어 페인팅됩니다.
7. 컴포지팅(Compositing)
페인팅이 완료되면 각 레이어를 조합하여 최종 화면을 그리는 컴포지팅(Compositing) 과정을 수행합니다. 이 단계에서는 GPU를 사용하여, 더 빠르고 효율적으로 화면을 렌더링할 수 있도록 합니다.
이 과정은 사용자의 스크롤을 움직이는 행위나 애니메이션 등 실시간 인터랙션에서도 매우 중요한 역할을 합니다. 특히 will-change
, transform
, opacity
와 같은 CSS 속성은 GPU 가속을 이용하여 부드러운 애니메이션을 가능하게 합니다. (만약, GPU가 제한적인 머신인 경우 CPU를 사용합니다)
GPU 가속: 그래픽 처리 장치(GPU)를 활용해 컴퓨터의 성능을 향상시키는 기술
8. 화면 변경 시 Reflow와 Repaint
최초 렌더링 이후에 사용자와의 상호작용 혹은 JavaScript 조작, 윈도우 크기 변경 등의 이벤트가 발생할 때마다 화면을 다시 그리는 작업을 수행합니다.
이때 변화의 종류에 따라 다음 두 가지 작업 중 하나 또는 둘 다 수행합니다.
- Reflow
- Repaint
8.1 Reflow (Layout)
Reflow는 레이아웃을 다시 계산하는 과정을 말합니다. 요소의 위치나 크기가 달라지면 전체 혹은 일부 레이아웃을 다시 계산해야 합니다. 예를들어,
- 요소의 크기 변경 (width, height)
- 글자 수 변경
- 창 크기 변경 (resize)
- DOM 추가/삭제
- 폰트 변경
Reflow는 성능에 매우 큰 영향을 주는 작업입니다. 전체 문서에 영향을 줄 수 있기 때문에 자주 발생하지 않도록 하는 것이 성능 향상에 도움이 됩니다.
8.2 Repaint
Repaint
는 요소의 시각적 속성만 바뀌었을 때 발생합니다. 즉, 레이아웃은 유지되지만 시각적인 요소만 다시 그려야 할 때 수행됩니다.
예를들어,
- 색상 변경 (color, background-color)
- 그림자 (box-shadow)
- 투명도 (opacity)
- 글자 스타일 변경
Repaint는 Reflow보다 상대적으로 비용이 덜 듭니다.
Reflow는 대부분 Repaint도 유발하는 경우가 많기 때문에 Reflow를 최소화하는 것이 성능 향상에 중요합니다.