DevYoon

[JS] 브라우저 렌더링 과정 본문

언어/Javascript

[JS] 브라우저 렌더링 과정

gimewn 2022. 7. 24. 23:51

⚙️ 브라우저 렌더링 과정

Contents

1️⃣ 브라우저의 렌더링 과정

2️⃣ 브라우저 렌더링 과정에서 자바스크립트의 동작

3️⃣ 태그는 왜 태그 밑에 둬야 할까?

1️⃣ 브라우저의 렌더링 과정

post-thumbnail

렌더링이란?

HTML, CSS, JavaScript 등 개발자가 작성한 문서를 브라우저에서 출력하는 과정

렌더링 과정

1️⃣ 주소창에 URL 입력

2️⃣ DNS 서버에서 주소에 해당하는 IP를 반환 ➡️ 접속

3️⃣ 브라우저가 HTML, CSS, JS, 이미지, 폰트 파일 등 렌더링에 필요한 리소스를 요청➡️ 서버로부터 응답 받음
4️⃣ 브라우저의 렌더링 엔진이 서버로부터 응답받은 HTML, CSS를 파싱하여 DOM과 CSSOM을 생성 ➡️ 이들을 결합하여 렌더 트리 생성
5️⃣ 자바스크립트 엔진이 서버로부터 응답된 자바스크립트를 파싱하여 AST를 생성 ➡️ 바이트코드로 변환하여 실행

- 자바스크립트는 DOM API를 통해 DOM이나 CSSOM을 변경할 수 있음

- 변경된 DOM과 CSSOM은 다시 렌더 트리로 결합

6️⃣ 렌더 트리를 기반으로 HTML 요소의 레이아웃(위치와 크기)을 계산 ➡️ 브라우저 화면에 HTML 요소를 페인팅

1️⃣ Parshing : HTML 과 CSS 파일을 파싱 ➡️ 각각 트리를 생성

2️⃣ Style : 두 트리를 결합하여 렌더링 트리를 생성

3️⃣ Layout : 렌더링 트리에서 각 노드의 위치와 크기를 계산

4️⃣ Paint : 계산된 값을 이용해 각 노드를 화면상의 실제 픽셀로 변환하고, 레이어를 만듦

5️⃣ Composite : 레이어를 합성하여 실제 화면에 나타냄

Parshing

image1

  • HTML 파일을 해석하여 DOM(Document Object Model) Tree를 구성
  • HTML에 CSS 포함 시 CSSOM(CSS Object Model) Tree도 함께 구성

Style

image2

  • DOM Tree와 CSSOM Tree를 매칭 ➡️ Render Tree 구성 (실제 화면에 그려질 트리)

​ *️⃣ meta 태그, script 태그 등 화면에 렌더링되지 않는 노드는 Render Tree에 포함 ❌

​ *️⃣ visibility: hidden / invisible ➡️ 공간을 차지하고 보이지만 않으므로 Render Tree에 포함 ⭕

​ display: none ➡️ Render Tree에 포함 ❌

Layout

  • Render Tree를 화면에 어떻게 배치할지 뷰포트 내에서 각 노드의 정확한 위치와 크기를 계산
    • 뷰포트(Viewport)
      • 그래픽이 표시되는 브라우저의 영역
      • 모바일 - 디스플레이의 크기 / PC - 브라우저 창의 크기
  • 루트부터 노드 순회 ➡️ 정확한 크기와 위치 계산 ➡️ Render Tree에 반영
  • 크기 값을 %로 지정했다면 이 단계에서 픽셀로 변환해줌

Paint

  • Layout 단계에서 계산된 값을 이용해 실제 화면에 그림
    • 여러 개의 레이어로 관리

2️⃣ 브라우저 렌더링 과정에서 자바스크립트의 동작

  • DOM은 HTML 문서의 구조와 정보, HTML 요소와 스타일 등을 변경할 수 있는 프로그래밍 인터페이스로서 DOM API를 제공함.

  • 자바스크립트 코드에서 DOM API를 사용하면 DOM을 동적으로 조작할 수 있음

  • 렌더링 엔진이 HTML을 한 줄씩 순차적으로 파싱하여 DOM을 생성하다가 자바스크립트 파일을 로드하는 script 태그나 자바스크립트 코드를 만나면 DOM 생성 일시 중단

  • 자바스크립트 엔진에 제어권 넘김 ➡️ 자바스크립트 코드 파싱 ➡️ AST (추상적 구문 트리) 생성

  • AST 기반으로 인터프리터가 실행할 수 있는 바이트 코드를 생성하여 실행

  • 이후 HTML 파싱 중당된 지점부터 다시 HTML 파싱 시작

3️⃣ script 태그는 왜 < body > 태그 밑에 둬야 할까?

  • 렌더링 엔진과 자바스크립트 엔진은 직렬적으로 파싱을 수행
  • 즉, 위에서 아래로 순차적으로 HTML, CSS, JS를 파싱하는 것
  • script 태그의 위치가 어디냐에 따라 HTML 파싱이 블로킹되어 DOM 생성이 지연될 수 있음

예시

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="style.css">
    <script>
      /*
      DOM API인 document.getElementById는 DOM에서 id가 'apple'인 HTML 요소를
      취득한다. 아래 DOM API가 실행되는 시점에는 아직 id가 'apple'인 HTML 요소를 파싱하지
      않았기 때문에 DOM에는 id가 'apple'인 HTML 요소가 포함되어 있지 않다.
      따라서 아래 코드는 정상적으로 id가 'apple'인 HTML 요소를 취득하지 못한다.
      */
      const $apple = document.getElementById('apple');

      // id가 'apple'인 HTML 요소의 css color 프로퍼티 값을 변경한다.
      // 이때 DOM에는 id가 'apple'인 HTML 요소가 포함되어 있지 않기 때문에 에러가 발생한다.
      $apple.style.color = 'red'; // TypeError: Cannot read property 'style' of null
    </script>
  </head>
  <body>
    <ul>
      <li id="apple">Apple</li>
      <li id="banana">Banana</li>
      <li id="orange">Orange</li>
    </ul>
  </body>
</html>
  • script는 다운로드되고 실행되는 동안 HTML 파싱 차단
  • DOM이 완성되지 않은 상태에서 자바스크립트가 DOM 조작 ➡️ 에러 발생 가능성 있음
  • 자바스크립트 로딩 / 파싱 / 실행으로 인해 HTML 요소들의 렌더링에 지장 ➡️ 페이지 로딩 시간 증가

✏️ script 태그는 body 태그 밑에 두자!

*️⃣ 리플로우 & 리페인트

  • 자바스크립트 코드에 DOM이나 CCSOM을 변경하는 DOM API가 사용 ➡️ DOM이나 CSSOM이 변경됨
  • 변경된 DOM과 CSSOM은 다시 렌더 트리로 결합 ➡️ 이를 기반으로 레이아웃과 페인트 과정을 거쳐 화면에 다시 렌더링

리플로우

  • 레이아웃 계산을 다시 하는 과정
  • 노드 추가 및 삭제, 요소 크기 및 위치 변경 등 레이아웃에 영향을 주는 변경이 발생한 경우에 한하여 실행
  • 단순 색상 변경과 같은 경우에는 리플로우 실행 ❌

리페인트

  • 재결합된 렌더 트리를 기반으로 다시 페인트를 하는 과정

리플로우 & 리페인트 ⭕

position, width, height, margin, padding, border, border-width, font-size, font-weight, line-height, text-align, overflow

리플로우 ❌ 리페인트 ⭕

background, color, text-decoration, border-style, border-radius

[부록] 웹사이트 최적화를 위한 적절한 방법

  • head 안에 link 넣기

  • body 밑이나 /body 직전에 script 넣기

왜 head 안에 link를 넣는 게 좋을까?

  • 페이지가 처음 로드되면 HTML과 CSS가 동시에 파싱됨
  • DOM과 CSSOM 모두 사이트의 시각적인 부분을 만드는데 필요 ➡️ First Meaningful Paint를 할 수 있게 해줌
    • First Meaningful Paint는 사이트의 성능 지표 중 하나