브라우저 렌더링 과정?
결론부터 말하자면, 상황에 따라 더 효율적이고 최적화된 웹 페이지를 작성하기 위해 브라우저 렌더링 과정을 이해하는 것이 중요합니다. 렌더링 과정을 알면 페이지 로딩 속도, UX, 성능 최적화 측면에서 큰 차이를 만들 수 있습니다.
웹 브라우저는 HTML + CSS + JavaScript로 구성된 코드를 읽고 렌더링합니다. 따라서, 우리는 브라우저가 어떤 순서로 어떻게 파일을 해석하고 나타내는지를 이해해야 합니다.
브라우저 렌더링 과정
렌더링 과정을 알아보기전 파싱, 렌더링, DOM이 무엇인지 이해해야 합니다.
파싱(Parsing)
파싱은 프로그래밍 언어 문법에 맞게 작성된 텍스트 파일을 기계가 이해할 수 있는 구조로 변환하는 과정입니다. 웹 페이지는 HTML, CSS, JavaScript 등 다양한 파일로 구성이 되어있고, 이를 구조화된 객체 모델로 변환하는 파싱 작업을 거쳐야 합니다.
렌더링(Rendering)
HTML, CSS, JS로 작성된 문서를 파싱해서 사용자가 볼 수 있도록 시각적으로 브라우저에 출력하는 과정입니다.
DOM(Document Object Model)
HTML 문서를 파싱한 자료구조의 결과물입니다. 예를 들어 <div>Hello, World!<div>라는 HTML 요소는 DOM에서 div라는 객체로 변환되고, 그 안에 "Hello, World!"라는 텍스트 노드가 들어가는 구조로 표현됩니다.
렌더링 엔진은 HTML을 한줄씩 읽으며 DOM을 생성합니다. 도중 CSS와 같은 Style을 만나면 DOM 생성을 중단하고 CSSOM 트리를 생성하게 됩니다. 해당 작업이 완료되면 DOM 생성 중단 시점으로 돌아가 작업을 이어가게 됩니다. 렌더트리(Render Tree) 생성 DOM과 CSSOM 생성이 완료되면 렌더 트리로 결합됩니다. 렌더 트리는 화면에 렌더링 되는 노드로만 구성됩니다. 예시
<!-- HTML -->
<div class="container">
<h1>Welcome</h1>
<p>This is an example of render tree generation.</p>
<p style="color: red;">This text is red.</p>
</div>
<!-- CSS -->
.container {
display: block;
background-color: lightgrey;
}
h1 {
font-size: 2em;
color: blue;
}
p {
font-size: 1em;
color: black;
}
이와 같은 HTML과 CSS 가 있다면
<!-- DOM -->
└── div.container
├── h1
└── p
└── p (style: color: red)
<!-- CSSOM -->
├── .container (display: block, background-color: lightgrey)
├── h1 (font-size: 2em, color: blue)
└── p (font-size: 1em, color: black)
└── p (style: color: red)
DOM과 CSSOM은 각각 위와 같이 형성이 됩니다. 이후 둘은 결합되어 렌더트리는 형성하게 되고 형태는 아래와 같습니다.
<!-- Render Tree -->
└── RenderNode (div.container)
├── RenderNode (h1, color: blue)
└── RenderNode (p, color: red)
브라우저 렌더링 반복(리렌더링)
자바스크립트의 수정, HTML 요소의 변경, CSS 스타일의 변경 등 다양한 이유로 인해 렌더링은 반복적으로 일어날 수 있습니다.
때문에 리렌더링은 성능에 큰 영향을 주는 작업이기 때문에 주의해야 합니다. 위에서 HTML 요소를 파싱하는 과정에 CSS를 만나면 중단하고 CSSOM을 생성한다고 했습니다. 마찬가지로 DOM 트리 생성중 JavaScript를 만난다면 렌더링 엔진은 자바스크립트 엔진에게 제어권을 넘기게 됩니다. 자바스크립트 파싱이 종료되면 다시 DOM 트리 생성이 중단된 시점으로 돌아가 작업을 시작하게 됩니다.
때문에 리렌더링을 생각하며 스크립트를 작성하는 것이 중요합니다.
또한, Script의 위치에 따라서 문제가 발생할 수 있습니다. 만약, Script에서 DOM API를 사용한다면 오류가 발생할수도 있습니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DOM API Example</title>
<style>
/* 초기 스타일 */
#world {
color: blue;
}
</style>
<script>
// DOM API를 사용하여 'world' ID를 가진 요소를 찾으려 합니다.
const element = document.getElementById('world');
if (element) {
element.innerText = 'Hello, World!';
} else {
console.error('ID가 "world"인 요소를 찾을 수 없습니다.');
}
</script>
</head>
<body>
<div id="world">이 문장은 초기 텍스트입니다.</div>
</body>
</html>
이와 같은 코드의 경우 Script를 파싱중 getElementById('world')를 찾게 됩니다. 하지만 DOM 트리가 생성되기 이전에 동작하기 때문에 콘솔창에 'ID가 "world"인 요소를 찾을 수 없습니다.' 와 같은 에러가 찍히게 됩니다.
이러한 이유로 인해 Script는 body요소의 최하단에 작성하는 것이 권장됩니다. 또한, CSS는 head에 위치시켜 HTML과 함께 스타일을 적용하여 렌더링을 하는것이 권장됩니다.