카테고리 없음

브라우저 렌더링 과정과 DOM

taek2-0310 2026. 6. 1. 13:07

1. 브라우저 렌더링 과정 개요

[요청] → [HTML 파싱 → DOM] → [CSS 파싱 → CSSOM]
       → [렌더 트리 생성] → [레이아웃] → [페인트]
단계설명
1. 요청/응답 DNS를 통해 IP 주소를 확인하고 서버에 리소스 요청
2. DOM 생성 바이트 → 문자 → 토큰 → 노드 → DOM 트리
3. CSSOM 생성 CSS 파일을 동일한 파싱 과정으로 CSSOM 트리 생성
4. JS 실행 자바스크립트 엔진이 AST 생성 → 바이트코드 실행
5. 렌더 트리 DOM + CSSOM 결합 (화면에 표시되는 노드만 포함)
6. 레이아웃 각 요소의 위치와 크기 계산
7. 페인트 픽셀로 화면에 그리기

2. 요청과 응답

브라우저 주소창에 URL을 입력하면:

  1. URL의 호스트 이름이 DNS를 통해 IP 주소로 변환된다.
  2. 해당 IP 주소의 서버에 GET 요청을 전송한다.
  3. 루트 요청(/)의 경우 서버는 기본적으로 index.html을 응답한다.
 
https://www.example.com:80/docs/search?category=js#intro
   │           │          │      │           │        │
 scheme      host        port   path        query  fragment

핵심: index.html을 파싱하는 중 <link>, <img>, <script> 태그를 만나면 해당 리소스를 서버에 추가로 요청한다.

3. HTTP 1.1 vs HTTP 2.0

구분HTTP/1.1HTTP/2.0
요청/응답 커넥션당 하나씩 처리 커넥션당 다중 처리 가능
전송 방식 순차적 (개별 전송) 동시 전송
속도 느림 약 50% 빠름

4. HTML 파싱과 DOM 생성

서버에서 HTML을 바이트로 응답받아 다음 과정을 거쳐 DOM 트리를 생성한다.

바이트(2진수)
    ↓ (meta charset 기준 디코딩)
문자열
    ↓ (어휘 분석 · Lexical Analysis)
토큰 (문법적 의미를 가지는 코드의 최소 단위)
    ↓
노드 (문서 노드 / 요소 노드 / 어트리뷰트 노드 / 텍스트 노드)
    ↓
DOM 트리

 

DOM 트리 예시

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <ul>
      <li id="apple">Apple</li>
      <li id="banana">Banana</li>
    </ul>
    <script src="app.js"></script>
  </body>
</html>
document
  └── html
        ├── head
        │     ├── meta [charset="UTF-8"]
        │     └── link [rel="stylesheet" href="style.css"]
        └── body
              ├── ul
              │    ├── li [id="apple"] → "Apple"
              │    └── li [id="banana"] → "Banana"
              └── script [src="app.js"]

5. CSS 파싱과 CSSOM 생성

렌더링 엔진이 <link> 또는 <style> 태그를 만나면:

  1. DOM 생성을 일시 중단한다.
  2. CSS 파일을 서버에 요청한다.
  3. HTML과 동일한 파싱 과정(바이트 → 문자 → 토큰 → 노드)을 거쳐 CSSOM 트리를 생성한다.
  4. CSS 파싱 완료 후 HTML 파싱을 재개한다.

6. 렌더 트리 생성

DOM + CSSOM을 결합하여 렌더 트리를 생성한다.

렌더 트리에서 제외되는 노드:

  • <meta>, <script>, <head> 같은 화면에 표시되지 않는 노드
  • CSS display: none이 적용된 노드

visibility: hidden은 공간은 차지하지만 보이지 않으므로 렌더 트리에 포함된다.

렌더 트리 완성 후:

  • 레이아웃(Layout): 각 요소의 위치(x, y)와 크기(width, height) 계산
  • 페인트(Paint): 계산된 정보로 픽셀을 화면에 그리기

7. 자바스크립트 파싱과 실행

HTML 파싱 중 <script> 태그를 만나면:

  1. DOM 생성을 일시 중단한다.
  2. 자바스크립트 파일을 서버에 요청한다.
  3. 자바스크립트 엔진에 제어권을 넘긴다.
  4. JS 엔진이 코드를 파싱하여 **AST(Abstract Syntax Tree)**를 생성한다.
  5. AST를 기반으로 바이트코드를 생성하고 실행한다.
  6. 실행 완료 후 렌더링 엔진으로 제어권 반환 → HTML 파싱 재개

8. 리플로우와 리페인트

자바스크립트에서 DOM API로 DOM이나 CSSOM을 변경하면 렌더 트리가 재생성되며 다음이 발생한다.

구분설명발생 조건
리플로우(Reflow) 레이아웃 계산을 다시 수행 요소 추가/삭제, 크기/위치 변경, 창 리사이징
리페인트(Repaint) 재결합된 렌더 트리로 다시 페인트 리플로우 이후, 또는 색상 등 레이아웃 영향 없는 변경

중요: 레이아웃에 영향을 주지 않는 변경(색상, 투명도 등)은 리페인트만 발생한다. 리플로우 + 리페인트는 비용이 크므로 가급적 최소화해야 한다.

9. script 태그 위치와 async/defer

문제: <head> 안에 <script> 배치 시

<head>
  <script src="app.js"></script>  <!-- DOM이 완성되기 전에 실행! -->
</head>
<body>
  <div id="app">...</div>
</body>
 
// app.js
const $app = document.getElementById('app'); // null → 에러 발생!
$app.style.color = 'red'; // TypeError

해결책 1: </body> 직전에 배치

<body>
  <div id="app">...</div>
  <script src="app.js"></script>  <!-- DOM 완성 후 실행 -->
</body>

해결책 2: async / defer 어트리뷰트

<script async src="app.js"></script>
<script defer src="app.js"></script>
구분HTML 파싱JS 로드JS 실행 시점
기본 중단 순차 로드 즉시
async 병렬 병렬 로드 완료 즉시 (순서 보장 X)
defer 병렬 병렬 HTML 파싱 완료 후 (순서 보장 O)

권장: DOM 조작이 필요한 스크립트는 defer 사용

10. DOM이란?

**DOM(Document Object Model)**은 HTML 문서를 파싱한 결과물로, 브라우저가 이해할 수 있는 트리 자료구조다.

노드의 4가지 핵심 타입

노드 타입설명
문서 노드 DOM 트리 최상위 루트. document 객체. 다른 노드에 접근하는 진입점
요소 노드 HTML 요소를 나타냄. 문서의 구조를 표현
어트리뷰트 노드 HTML 요소의 어트리뷰트. 부모 노드 없이 요소 노드에만 연결됨
텍스트 노드 HTML 요소의 텍스트. 리프 노드(자식 노드 없음). 문서의 정보를 표현

노드 상속

Object
  └── EventTarget
        └── Node
              ├── Document → HTMLDocument
              ├── Attr
              ├── CharacterData → Text / Comment
              └── Element → HTMLElement
                              ├── HTMLInputElement
                              ├── HTMLDivElement
                              └── ...

11. 노드 취득 메서드 비교

id로 취득

// 단 하나의 요소 반환. 없으면 null
document.getElementById('apple');

태그 이름으로 취득

// HTMLCollection 반환 (live)
document.getElementsByTagName('li');

// 특정 요소의 자손 중에서만 탐색
$ul.getElementsByTagName('li');

class로 취득

// HTMLCollection 반환 (live)
document.getElementsByClassName('fruit');

CSS 선택자로 취득 ✅ 권장

// 첫 번째 요소 하나 반환
document.querySelector('.banana');

// 조건을 만족하는 모든 요소 반환 → NodeList (non-live)
document.querySelectorAll('ul > li');

HTMLCollection vs NodeList 비교

구분HTMLCollectionNodeList
반환 메서드 getElementsByTagName, getElementsByClassName querySelectorAll, childNodes
live 여부 항상 live (실시간 반영) 대부분 non-live (childNodes는 live)
순회 시 주의 for 문 순회 중 변경되면 오작동 위험 비교적 안전
권장 사용법 배열로 변환 후 사용 [...$elems] 배열로 변환 후 사용 권장
// HTMLCollection 순회 시 주의사항
const $elems = document.getElementsByClassName('red'); // live!

// ❌ 위험: 순회 중 DOM이 변경되어 일부 요소가 건너뛰어짐
for (let i = 0; i < $elems.length; i++) {
  $elems[i].className = 'blue';
}

// ✅ 안전: 배열로 변환 후 순회
[...$elems].forEach(elem => elem.className = 'blue');

12. DOM 조작

텍스트 조작

// nodeValue: 텍스트 노드의 값을 직접 변경
const $text = document.getElementById('foo').firstChild;
$text.nodeValue = 'changed!';

// textContent: 요소의 모든 텍스트 취득/변경 (HTML 마크업 무시)
document.getElementById('foo').textContent = 'Hello World';

innerHTML vs insertAdjacentHTML vs createElement

방법장점단점
innerHTML 간단, 직관적 XSS 취약, 기존 자식 노드 전부 제거 후 재생성
insertAdjacentHTML 기존 노드 유지, 빠름 XSS 취약
createElement + appendChild 안전, XSS 위험 없음 코드 복잡
// insertAdjacentHTML 위치 옵션
element.insertAdjacentHTML('beforebegin', '<p>앞</p>');  // 요소 앞
element.insertAdjacentHTML('afterbegin',  '<p>첫째</p>'); // 첫 자식 앞
element.insertAdjacentHTML('beforeend',  '<p>마지막</p>');// 마지막 자식 뒤
element.insertAdjacentHTML('afterend',   '<p>뒤</p>');   // 요소 뒤

노드 생성 및 추가

// 여러 노드 추가 시 DocumentFragment 사용 → DOM 변경 1회로 최소화
const $fragment = document.createDocumentFragment();

['Apple', 'Banana', 'Orange'].forEach(text => {
  const $li = document.createElement('li');
  $li.textContent = text;
  $fragment.appendChild($li);
});

document.getElementById('fruits').appendChild($fragment);
// DOM 변경은 단 1회!

주요 노드 탐색 프로퍼티

// 자식 탐색
node.childNodes          // NodeList (텍스트 노드 포함)
element.children         // HTMLCollection (요소 노드만)
element.firstElementChild
element.lastElementChild

// 부모 탐색
node.parentNode

// 형제 탐색
element.previousElementSibling
element.nextElementSibling