성능 최적화를 위한 브라우저 렌더링 원리

이민영·2025년 1월 5일
post-thumbnail

🤔 성능 최적화를 하려면 왜 브라우저 렌더링 원리를 알아야 할까?

성능 최적화와 브라우저 렌더링 원리는 밀접하게 연관되어 있다. DOM, CSSOM, 그리고 렌더 트리 생성 및 업데이트 과정에서 발생하는 비용을 줄이는 것이 성능 최적화의 핵심인데, 이 과정에서 브라우저가 화면을 그릴 때 발생하는 다양한 비용을 최소화하는 것이 중요하다. 예를 들어, DOM 조작이나 CSS 변경, 레이아웃 계산, 애니메이션 등의 작업은 렌더링 과정을 트리거하는 요소들이다. 이러한 작업들을 최소화하면 브라우저는 더 적은 리소스를 사용해 화면을 그릴 수 있다.

따라서 브라우저 렌더링 원리를 이해하는 것이 웹 성능 문제를 해결하는 데 큰 도움이 된다. 렌더링 과정에서 어느 단계에서 성능 문제가 발생하는지 빠르게 파악할 수 있고, 문제의 원인을 정확히 이해한 뒤 해결 방법을 신속하게 찾을 수 있기 때문이다. 웹 성능 최적화를 위한 첫걸음은 바로 이 렌더링 원리를 이해하는 것이라고 할 수 있다.

✅ 브라우저 구성 요소

브라우저는 운영 체제(OS) 위에서 실행되는 애플리케이션으로, 사용자가 웹사이트를 탐색하고 정보를 검색하며 소통할 수 있도록 돕는다. 브라우저가 이러한 역할을 할 수 있는 이유는 다양한 구성 요소들이 협력하여 웹 페이지를 로드하고 렌더링하며 사용자와 상호작용하는 방식으로 작동하기 때문이다. 브라우저는 다음과 같은 주요 요소들로 구성된다.

1. 사용자 인터페이스(User Interface)
사용자가 브라우저와 상호작용하는 주요 인터페이스이다. 주소창, 뒤로 가기/앞으로 가기 버튼, 탭, 북마크, 메뉴 등 다양한 기능을 제공한다.

2. 브라우저 엔진 (Browser Engine)
사용자 인터페이스와 렌더링 엔진 사이의 인터페이스 역할을 한다. 주어진 URL 을 읽어, 앞/뒤 페이지로 가거나 새로고침을 하는 등 기초적인 액션을 담당한다.

3. 렌더링 엔진 (Rendering Engine)
HTML, CSS, JavaScript 파일을 해석하여 화면에 웹 페이지를 렌더링한다. 웹 페이지를 시각적으로 구성하는 핵심 엔진이다.
렌더링 엔진은 사용하는 브라우저마다 다르다.

Google : Blink
IE : Trident
FireFox : Gecko
Crome for IOS : WebKit

4. 네트워크 레이어(Network)
서버와의 통신을 담당하며, HTTP 요청 및 응답을 처리한다. 브라우저가 웹 페이지나 리소스를 요청하고, 이를 서버에서 받아오는 과정이 포함된다.

5. 자바스크립트 인터프리터(JavaScript Interpreter)
웹 페이지에서 동적으로 실행되는 JavaScript 코드를 해석하고 실행한다.웹 페이지 내의 JavaScript 코드를 실행하여 DOM을 조작하고, 사용자와의 상호작용을 처리한다.

6. ui 백엔드
os에서 제공하는 UI메서드를 사용해 버튼, 콤보박스 등 기본적인 위젯을 그려준다.

7. 스토리지 레이어
자료를 저장하는 레이어다. 쿠키나 로컬/세션 스토리지 등 모든 종류의 자원을 하드디스크에 저장한다.

** 사용자가 URI를 입력하면 브라우저 렌더링의 구성요소들이 어떻게 동작하는지까지 정리하고 싶지만 오늘의 주제인 렌더링 원리에서는 좀 벗어나는 거 같아 그건 따로 적지 않겠다!

✅ 브라우저 렌더링 원리

이제 렌더링 엔진이 리소스를 처리하고 UI를 화면에 표시하는 부분에 대해 좀 더 자세히 살펴보자.

1) HTML 파싱 → DOM Tree 생성
2) CSS 파싱 → CSSOM Tree 생성
3) DOM Tree + CSSOM Tree → Render Tree 생성
4) Layout: Render Tree 기반으로 요소의 위치와 크기 계산
5) Paint: Render Tree와 Layout 정보로 화면에 그릴 픽셀 준비
6) Composite: 픽셀을 화면에 최종 렌더링

1. DOM Tree 생성

DOM (Document Object Model) Tree는 HTML 문서를 브라우저가 파싱하여 계층적 트리 구조로 표현한 객체 모델이다. 과정은 아래와 같이 이루어진다.

1. HTML 문서로드

  • 브라우저는 네트워크 요청을 통해 HTML 문서를 서버로부터 받아온다.
  • HTML은 문자열로 전송되며, 렌더링 엔진이 이를 처리하게 된다.

2. 토큰화 (Tokenization)

  • HTML 문자열이 HTML 파서에 의해 처리된다.
  • 파서는 HTML 문서를 순차적으로 읽으며, 토큰(Token)으로 분리한다.

3. 트리 생성 (Tree Construction)

  • 토큰화된 결과를 바탕으로 DOM Tree가 점진적으로 구축된다. 따라서 HTML 문서가 완전히 로드되지 않아도 DOM Tree는 생성 중일 수 도 있다.
  • HTML 태그는 최상위 노드(Node)로 변환되며, 노드는 부모-자식 관계를 통해 트리 구조를 형성한다.
  • DOM Tree는 트리구조이기에 이후 렌더링 단계에서 각 노드들의 위치와 크기는 부모 노드를 기준으로 하여 상대적으로 작성할 수 있다.

예시 코드

<html>
  <head>
    <title>Document</title>
  </head>
  <body>
    <div>
      <p>Hello, World!</p>
    </div>
  </body>
</html>

예시 DOM 트리 구조

html
├── head
│   └── title
└── body
    └── div
        └── p
            └── "Hello, World!"

HTML 문서를 모두 파싱한 후 최종적으로 DOM Tree가 완성된다.

2. CSSOM 생성

CSSOM (CSS Object Model) Tree는 CSS 문서를 파싱하여 브라우저가 스타일 정보를 표현하는 계층적 트리 구조이다. 과정은 아래와 같이 이루어진다.

1. CSS 리소스 로드

  • 브라우저는 HTML 문서에서 <style> 태그나 <link> 태그를 만나면 CSS 리소스를 가져온다.
  • 네트워크 요청을 통해 외부 CSS 파일을 가져오거나, HTML에서 CSS 코드를 바로 읽어들인다.

2. CSS 파싱 시작

  • 로드된 CSS 코드(문자열 형식)는 CSS 파서에 의해 처리된다.
  • CSS 파서는 스타일 규칙을 토큰(Token)으로 분리한다.

3. 토큰 → 스타일 규칙 변환

  • 파싱된 토큰은 스타일 규칙으로 변환된다.
  • 각 스타일 규칙은 다음 두 가지를 포함한다:

선택자: 스타일이 적용될 HTML 요소
선언 블록: 선택자에 적용될 스타일 속성과 값

4. CSSOM Tree 생성

  • 스타일 규칙이 계층적 구조로 구성되어 CSSOM Tree를 형성한다.
  • CSSOM Tree는 선택자와 그에 따른 스타일 속성을 연결한 구조이다.

CSSOM Tree

- body
  ├── margin: 0
  └── font-size: 16px
- h1
  ├── color: blue
  └── text-align: center

모든 CSS를 파싱하고 상속, 우선순위, 미디어 쿼리 등을 처리한 뒤 최종적으로 CSSOM Tree를 완성한다.

3. Render Tree 생성

Render Tree는 브라우저가 화면에 요소를 배치하고 그릴 때 필요한 구조로, DOM Tree와 CSSOM Tree를 결합하여 생성된다.Render Tree는 화면에 실제로 표시되어야 하는 노드만 포함하며, 각 노드에 스타일과 레이아웃 정보를 담는다.

DOM Tree와 CSSOM Tree 결합

  • Render Tree 는 DOM 의 형태를 순회하며 각 노드들이 CSSOM Tree에서 정의된 관계 형태를 따르는지를 확인하며 해당하는 스타일 규칙을 적용한다.

시각적 노드만 포함

  • Render Tree는 실제로 화면에 렌더링되는 요소들만 포함한다. 다음과 같은 요소는 Render Tree에 포함되지 않는다.

    display: none;: 완전히 숨겨지는 요소.(DOM Tree와 CSSOM Tree에는 포함되지만, Render Tree에는 포함되지 않음)
    메타 태그와 같은 시각적으로 표시되지 않는 요소.

Render Tree 생성

  • Render Tree는 화면 렌더링의 핵심 중간 데이터로, 사용자가 보여지는 최종 화면 구성의 틀이다.

4. Layout

Layout은 Render Tree를 기반으로 각 요소의 위치와 크기를 계산하는 단계이다. 이 과정은 박스 모델(Box Model)텍스트을 적용하여 요소들이 화면에 어떻게 배치될지 결정한다.

상대적 크기와 위치 계산

  • Render Tree의 각 노드를 탐색하며 부모-자식 관계를 기준으로 요소의 위치와 크기를 계산한다.
  • 탐색 중에 %, em, rem 등 상대 단위를 픽셀 값으로 변환한다.
  • 상속되는 CSS 속성들도 이 단계에서 구체적인 값으로 평가된다.

플로우와 컨텍스트 설정

  • Normal Flow, Flexbox, Grid, Positioning 등 레이아웃 방식에 따라 위치와 크기를 계산한다.

뷰포트와 스크롤 영역 계산

  • 요소들이 화면(뷰포트) 내에 어떻게 보이는지를 계산한다.
  • 콘텐츠가 뷰포트를 넘어서면, 스크롤 영역이 설정된다.

❗️ Layout은 문서 전체 또는 일부를 다시 계산해야 하므로 성능에 큰 영향을 미친다.

5. Paint

Paint는 Layout 단계에서 계산된 요소의 위치와 스타일을 기반으로 각 요소를 픽셀 단위로 표현하는 단계이다.
브라우저는 이 단계에서 요소의 시각적 속성(색상, 그림자, 배경, 텍스트 등)을 픽셀 단위로 계산하고 그린다.

각 요소에 대한 스타일 처리

  • Render Tree를 탐색하며 화면에 그릴 요소를 처리한다.
  • 텍스트 색상, 테두리, 배경색, 그림자 등을 계산하여 화면에 표현한다.

레이어 생성

  • Paint 과정에서는 레이어별로 나뉘어 그려질 요소를 결정한다.
  • 레이어는 z-index, CSS 속성(예: transform, will-change) 등에 의해 생성된다.

6. Composite

Composite 단계는 GPU가 여러 레이어에서 준비된 픽셀 데이터를 결합하여 최종적으로 화면에 렌더링하는 단계이다. 레이어 수가 많을수록 합성이 오래 걸리기 때문에 레이어 수가 많으면 성능에 좋지 않다.

✅ 각 렌더링 과정에서 성능에 영향을 미치는 요소와 해결책

1.DOM Tree 생성시 성능에 영향을 미치는 요소

DOM Tree를 생성하는 동안 JavaScript 파일을 동기적으로 불러오면 HTML 파싱이 중단된다.
❓ 브라우저는 HTML 문서를 위에서 아래로 순차적으로 파싱하며, 이 과정에서 <script> 태그를 만나면 잠시 파싱을 중단한다. 이는 JavaScript 코드가 HTML 문서의 상태에 영향을 줄 수 있기 때문이다. 브라우저는 해당 스크립트를 로드하고 실행한 후에야 다시 HTML 파싱을 재개하므로, 전체 파싱 시간이 늘어날 수 있다.

  1. <script> 태그는 가능한 한 HTML의 body 끝에 위치하도록 하자.

  2. 비동기 로딩을 하자.
    defer 속성 사용 :
    HTML 파싱이 완료된 후 스크립트가 실행된다. 순차적인 실행이 필요할 때 유용하다.
    <script src="script.js" defer></script>
    async 속성 사용:
    JavaScript 파일이 병렬로 로드되며, 로드 완료 즉시 실행된다. 주로 독립적인 스크립트에 적합하다.
    <script src="script.js" async></script>

2. CSSOM Tree 생성시 성능에 영향을 미치는 요소

CSS 파일의 크기와 복잡성이 CSSOM 트리 생성 성능에 영향을 미친다.
❓ 브라우저는 HTML을 파싱하면서 스타일 시트를 로드하여 CSSOM 트리를 생성한다. 이 과정에서 CSS 파일이 너무 크거나 복잡하면 브라우저가 이를 해석하는 데 시간이 더 걸리게 된다. 특히, 여러 개의 CSS 파일을 불러오거나 복잡한 선택자가 많을 경우, CSSOM 트리 생성 시간이 길어져 렌더링 성능에 영향을 줄 수 있다.

1. CSS 파일 크기는 가능한 한 최적화하자.
여러 개의 CSS 파일을 병합하여 하나의 파일로 만드는 것이 HTTP 요청 수를 줄이고 로딩 시간을 단축시킬 수 있다. (** SSR일 경우)

2. 사용하지 않는 CSS는 제거하자.
사용하지 않는 CSS를 제거하면, 불필요한 스타일 규칙이 CSSOM 트리에 포함되지 않아 CSSOM 트리 크기를 줄인다. 또한, 파일 크기가 줄어들어 페이지 로딩 시간을 단축할 수 있다.

3. CSS 복잡성 최소화하기.
복잡한 선택자나 스타일 규칙이 많으면 브라우저가 이를 해석하는 데 시간이 더 걸린다. !important나 중첩된 선택자들을 과도하게 사용하는 경우 성능에 악영향을 미칠 수 있다.

3. Render Tree 생성시 성능에 영향을 미치는 요소

CSS 선택자가 깊이가 깊거나 불피요한 태그가 중첩되어 있으면 Render Tree 생성이 느려질 수 있다.
❓ Render Tree를 생성하려면 브라우저는 HTML의 각 요소(DOM 노드)에 적합한 스타일을 적용하기 위해 CSSOM과 DOM Tree를 결합해야 한다. 이 과정에서 브라우저는 CSS 선택자를 DOM 요소에 일치시키는 작업을 수행힌다. 예를 들어, header > div > ul > li > span > p와 같은 CSS 선택자로 스타일이 정의된 경우, 브라우저는 p 태그를 만날 때마다 모든 계층의 부모 요소를 확인해야 하므로 연산 비용이 증가한다. 또한, 태그가 불필요하게 중첩되어 있다면 스타일을 적용하는 과정에서 부모 요소를 하나하나 확인해야 하므로 성능이 더욱 저하될 수 있다.

1. CSS 선택자를 단순화하고 클래스를 활용하자.
복잡한 CSS 선택자 header > div > ul > li > span > p 대신 클래스나 ID를 사용하자.

/* 비효율적 */
header > div > ul > li > span > p { color: red; }
/* 효율적 */
.highlight { color: red; }

2. 복잡한 DOM 트리를 지양하자.
DOM 트리가 깊고, 자식 요소가 많을수록 DOM 트리는 커지고 그에 따른 계산이 추가되므로, 불필요한 태그 중첩을 최소화 하자.

/* 비효율적 */
<div>
  <div>
    <div>
      <p>텍스트</p>
    </div>
  </div>
</div>
/*효율적*/
<div>
  <p>텍스트</p>
</div>

4. Layout 과정 중 성능에 영향을 미치는 요소

불필요한 리플로우(리렌더링)를 최소화 해야 한다.
❓ 리플로우(Reflow)는 DOM의 구조나 스타일에 변화가 생기면 브라우저가 새로 레이아웃을 계산하는 과정을 말한다. 브라우저는 요소의 크기, 위치, 정렬 등을 계산하여 최종 화면에 표시될 요소들을 결정하는데, 이 과정에서 많은 계산과 메모리 작업이 발생한다. 특히, 레이아웃이 여러 번 재계산되면 성능에 큰 부하를 일으킬 수 있다.

1. DOM 변경을 한번에 처리하자.
여러 개의 DOM 변경이 필요한 경우, 한 번에 처리하여 리플로우를 최소화해야 한다. DOM을 여러 번 수정하는 것보다 한 번에 수정하면 브라우저는 레이아웃을 한 번만 계산하게 된다.

2. 상위 요소에서 레이아웃 변경을 최소화하자.
부모 요소나 상위 요소의 레이아웃이 변경되면 자식 요소의 레이아웃도 함께 변경되어 성능에 영향을 미친다. 가능한 한 자식 요소에만 스타일을 변경하거나, 부모 요소의 크기나 위치를 고정하여 상위 요소의 레이아웃 변경을 피하는 것이 좋다.

3.스타일을 동적으로 변경할 때 GPU에서 처리하도록 하자.
스타일을 동적으로 변경할 때, 특히 width, height, padding, margin, top, left 등의 레이아웃에 영향을 주는 스타일 속성은 리플로우를 유발한다.
그러나 transformopacity 속성은 레이아웃에 영향을 미치지 않고, GPU에서 하드웨어 가속을 받아 처리되기 때문에 애니메이션이나 스타일 동적으로 변경할 때는 transformopacity를 사용하는 것이 좋다.

4. 요소를 숨길때 visibility: hidden 사용하자.
display: none은 요소 자체를 숨기기 때문에 DOM 변경이 일어나기 때문에, 요소를 숨길 때 display: none을 사용하는 것보다 visibility: hidden을 사용하는 것이 성능에 유리하다.

5. Reflow를 발생시키는 CSS 속성을 피하자.

box-sizing / border-color / text-align / border / border-width / 
font-family / float / font-size / font-weight / line-height / vertical-align / 
white-space / word-wrap / text-overflow / text-shadow ...

5. Paint 과정 중 성능에 영향을 미치는 요소

불필요한 리페인트를 최소화 해야 한다.
❓ 리페인트(Paint)는 요소의 스타일이나 색상이 변경될 때 발생하며, 리플로우보다는 비용이 적게 들지만 여전히 성능에 영향을 미칠 수 있다. 브라우저는 스타일이나 색상 변경에 따라 화면에 다시 그리기를 수행하며, 이 과정이 반복되면 화면 깜빡임이나 렌더링 지연이 발생할 수 있다. 불필요한 리페인트를 최소화하면 성능을 최적화하고, 더욱 부드러운 사용자 경험을 제공할 수 있다.

1. 스타일을 변경을 한 번에 처리하자.
스타일 변경은 리페인트를 유발하므로, 자주 스타일을 변경하는 것은 성능에 영향을 미친다. 여러 번 스타일을 변경하기보다는, 한 번에 여러 스타일을 변경하는 것이 좋다.

2. Repaint를 발생시키는 CSS 속성을 피하자.

background-image / background-position / background-repeat / background-size / 
text-decoration / outline / outline-style / outline-color / outline-width / 
border-radius / box-shadow ...


혹시라도 잘못된 내용이 있거나 질문사항 있으시면 댓글 남겨주세요! 어떤 댓글이든 달게 받겠습니다!
profile
Frontend Developer

0개의 댓글