Study/CS

브라우저의 렌더링 과정

다니니니 2024. 11. 25. 19:17
728x90

들어가며

이전에 면접 질문으로도 받았었고 프론트엔드 개발자 뿐만 아니라 웹 개발자라면 알아야 할 거라 생각해서 정리해보려 한다.

 

브라우저 렌더링

브라우저의 렌더링 순서

  1. 브라우저는 HTML, CSS, 자바스크립트, 이미지, 폰트 파일 등 렌더링에 필요한 리소스를 요청하고 서버로부터 응답받는다.
  2. 서버로부터 HTML 문서를 전달받으면, 브라우저 엔진은 위에서 아래로 순차적으로 파싱하며 태그와 속성을 발견한다
  3. 이 태그와 속성들을 트리 형태로 변환해서 메모리에 저장되는데, 이를 DOM Tree 라고 한다.
  4. HTML 파싱 중 CSS 를 만나면 이를 파싱해서 CSSOM Tree 로 변환한다.
  5. DOM tree 와 CSSOM tree 를 결합해서 Render Tree 를 생성한다.
  6. 렌더 트리를 기반으로 html 요소의 레이아웃(위치와 크기) 을 계산하고, 브라우저 화면에 html 요소를 페인팅한다.

 

 

DOM 생성

DOM(Document Object Model) 은 웹 페이지의 구조를 표현한 객체 모델로, 노드로 이루어졌다.

 

다음과 같은 html 문서가 있다고 했을 때

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="style.css">
    <title>Document</title>
</head>
<body>
    <ul>
        <li id="apple">Apple</li>
        <li id="banana">Banana</li>
        <li id="orange">Orange</li>
    </ul>
    <script src="app.js"></script>
</body>
</html>

 

브라우저 엔진은 다음 과정을 거쳐서 html 문서를 파싱해서 DOM을 생성한다.

HTML 파싱 / 출처 : 모던 자바스크립트 Deep Dive

  1. 서버에 있는 html 파일이 브라우저 요청에 의해 응답됨
    서버는 브라우저가 요청한 html 파일을 바이트(2진수) 형태로 응답(바이트화)
  2. 바이트 형태로 응답받은 html 문서를 meta 태그의 charset 어트리뷰트에 지정된 인코딩 방식(ex. UTF-8)을 기준으로
    문자열로 변환(캐릭터화)
  3. 문자열로 변환된 html 문서를 읽어들여서 토큰화 시킴
  4. 각 토큰들을 객체로 변환해서 노드들을 생성
    (토큰 내용에 따라 문서 노드, 요소 노드, 어트리뷰트 노드, 텍스트 노트가 생성됨)
  5. 만들어낸 노드들을 관계화 시켜서(예. 부모-자식 관계 설정) 트리 자료 구조로 구성함.
    이 트리 자료구조를 DOM(Document Object Model) 이라 함

DOM 구조 / 출처 : 모던 자바스크립트 Deep Dive

 

.

 

CSSOM 생성

렌더링 엔진은 DOM 을 생성하다가 CSS 를 로드하는 link 태그나 style 태그를 만나면 DOM 생성을 일시 중단한다.

그리고 link 태그를 만난다면 href 에 어트리뷰트 지정된 CSS 파일을 서버에 요청한다.

로드한 CSS 파일이나 style 태그 내의 CSS 를  HTML 과 동일한 파싱 과정(바이트 -> 문자 -> 토큰 -> 노드 -> CSSOM) 을 거쳐서 CSSOM(CSS Object Model) 을 생성한다.

 

CSS 파싱을 완료하면 HTML 파싱이 중단된 지점부터 다시 HTML을 파싱하기 시작하여 DOM 생성을 한다.

 

위의 html 파일에 지정된 style.css 코드가 다음과 같다고 가정해보자

body {
   font-size : 18px;
}

ul {
   list-style-type : none;
}

 

CSSOM은 CSS 상속을 반영해서 생성된다.

 

CSSOM / 출처 : 모던 자바스크립트 Deep Dive

 

 

렌더 트리 생성

DOM 트리와 CSSOM 트리를 결합해서 렌더 트리(Render Tree) 를 생성한다.

웹 사이트를 그리기 위한 최종 설계도라고 생각하면 된다.

이 때, body 태그 내에 있는 노드들과 CSS 에 의해 표시되는 노드들만 포함해서 만든다.

(head 태그 내의 meta 태그 등과 CSS 에 의해 비표시 되는 것 display : none 표시 되는 노드들은 포함하지 않는다.)

 

레이아웃

브라우저는 완성된 렌더 트리를 사용해서 각 요소의 정확한 위치와 크기를 계산한다.

이 과정을 레이아웃이라 한다.

레이아웃 과정에서 렌더 트리의 각 노드가 어디에 위치할지, 얼마나 큰지 계산한다.

 

이 계산은 화면의 뷰포트(viewport) 크기와 같은 정보에 의존한다.

예를 들어, 화면의 크기가 변경되면 브라우저는 레이아웃 과정을 다시 수행해야 한다.(리플로우)

 

페인팅

레이아웃이 완료되면, 브라우저는 각 요소를 실제로 화면에 그리는 작업을 한다.

페인팅 단계에서는 텍스트, 색상, 그림자, 이미지 등 모든 시각적 요소가 화면에 그려진다.

(화면에 표시될 그래픽 요소 생성)

 

렌더트리와 레이아웃, 페인트 / 출처 : 모던 자바스크립트 Deep Dive

 

이 과정은 반복해서 실행될 수 있다.

다음의 경우 레이아웃 계산과 페인팅이 재차 실행된다.

  • 자바스크립트에 의해 노드 추가 또는 삭제
  • 브라우저 창의 리사이징에 의한 뷰포트 크기 변경
  • HTML 요소의 레이아웃(위치, 크기)에 변경을 발생시키는 width/height, margin, padding, border, display, position, top/right/bottom/left 등의 스타일 변경

이렇게 변경된 DOM 트리와 CSSOM 트리는 다시 렌더트리로 결합되고 변경된 렌더 트리를 기반으로 레이아웃과 페인트 과정을 다시 거친다.

이를 리플로우(reflow), 리페인트(repaint) 라 한다.

 

리플로우(reflow)

브라우저가 페이지의 레이아웃을 다시 계산하는 과정이다.

노드의 추가나 삭제, 요소의 크기나 위치 변경, 윈도우 리사이징 등 레이아웃에 영향을 주는 변경이 발생한 경우애 한하여 실행된다.

 

리플로우가 될 때를 보면 다음과 같다

  • 페이지 초기 렌더링 시(최초 layout)
  • 윈도우 리사이징(viewport 크기 변경 시)
  • 노드 추가 또는 제거
  • 요소의 위치, 크기 변경(left, top, margin, padding, border, width, heigth)
  • 폰트 변경, 이미지 크기 변경 등

이 과정은 모든 자식 요소와 관련된 부모 요소까지 영향을 주기 때문에 비용이 많이 든다.

 

리페인트(repaint)

요소의 모양이나 스타일이 변경될 때 발생한다.

요소의 레이아웃은 그대로지만 색상이나 배경 등의 스타일만 변경되는 경우에 해당한다.

이 때 브라우저는 요소의 모양이나 색상만 다시 그리면 되기 때문에 리플로우보다는 비용이 덜 든다.

 

다시 말하면

리플로우는 레이아웃을 다시 계산하는 과정,

리페인트는 그 계산 결과를 화면에 다시 그리는 과정이다.

이 둘을 잘 이해하고 관리하면 성능 최적화에 도움이 된다.

 

성능 최적화 예시

  1. 리플로우(reflow) 를 유발하는 CSS 속성 사용 최소화
    width, height, margin, padding, border 등의 속성은 요소의 레이아웃을 다시 계산하므로 가능한 미리
    CSS 에서 스타일을 설정해서 초기 로드 시에만 계산이 이루어지도록 하고 이후에는 변경을 최소화하는 것이 좋다
  2. CSS 애니메이션 최적화
    애니메이션에 transform 과 opacity 속성을 사용하는 것이 좋다. 이 두 속성은 리페인트만 발생시킨다.
    위에서 말했다시피 width, height, left, top 등은 리플로우를 발생시키므로
    transform 의 속성을 이용하는 것이 성능에 유리하다 
  3. will-change 속성 사용
    CSS 의 will-change 속성을 사용해서 브라우저에 특정 요소가 변경될 것이라고 알려줄 수 있다.
    미리 GPU 에 요소를 준비하게 해서 리플로우 및 리페인트에 미치는 영향을 줄일 수 있다.
    하지만 너무 자주 사용하면 메모리 낭비가 일어나므로 필요한 요소에만 적용하는 것이 좋다.

 

자바스크립트 파싱에 의한 HTML 파싱 중단

HTML 파싱 중간에 script 태그를 만나면 브라우저는 해당 스크립트를 로드하고 실행하기 위해 HTML 파싱을 일시 중단한다.

즉, script 태그에 의해 블로킹이 발생할 수 있다.

외부 스크립트의 경우, 스크립트를 로드하고 실행한 후 파싱을 재개하고

내부 스크립트의 경우, 실행이 완료될 때까지 파싱이 중단된다.

 

이로 인해서 파싱 속도가 저하되고, DOM 트리가 완성되기 전에 자바스크립트가 DOM 을 조작하면 에러가 발생할 수 있다.

따라서 HTML 문서 내에서 script 태그의 위치가 중요하다.

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="style.css">
    <title>Document</title>
</head>
<body>
    <ul>
        <li id="apple">Apple</li>
        <li id="banana">Banana</li>
        <li id="orange">Orange</li>
    </ul>
    <script src="app.js"></script>
</body>
</html>

 

위의 코드 예제의 경우 sciprt 태그 즉, 자바스크립트 코드는 DOM 트리가 완성된 후에 실행된다.

따라서 자바스크립트가 DOM을 조작할 때 DOM트리가 완성되지 않아서 생기는 오류가 생기지 않는다.

또 위의 코드는 자바스크립트가 실행되기 이전에 DOM 생성이 완료되어 랜더링되므로 페이지 로딩 시간이 단축된다.

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="style.css">
    <title>Document</title>
    <script src="app.js"></script>
</head>
<body>
    <ul>
        <li id="apple">Apple</li>
        <li id="banana">Banana</li>
        <li id="orange">Orange</li>
    </ul>
</body>
</html>

 

그러나 위의 코드는 script 태그가 위에 존재하기 때문에 블로킹이 일어나서

 DOM조작 시 오류가 날 수 있다.

 

script 태그의 async, defer 속성

자바스크립트 파싱에 의한 DOM 생성 중단을 방지하기 위해 HTML5 부터 async와 defer 속성이 script 태그에 추가되었다.

이 두 속성은 외부 스크립트를 로드할 경우에만 사용할 수 있다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="style.css">
    <title>Document</title>
    <script defer src="app.js"></script>
</head>
<body>
    <ul>
        <li id="apple">Apple</li>
        <li id="banana">Banana</li>
        <li id="orange">Orange</li>
    </ul>
</body>
</html>

 

위의 코드처럼 script 태그에 defer (혹은 async ) 속성을 써주면 된다.

이 속성들을 사용하면 HTML 파싱과 외부 자바스크립트 파일의 로드가 비동기적으로 동시에 진행된다.

그러나 두 속성은 실행 시점에 차이가 있다.

 

async

자바스크립트 파싱과 실행은 자바스크립트 파일의 로드가 완료된 직후 진행한다.

여러 개의 script 에 async 속성을 지정하면 태그의 순서와 상관없이 로드가 완료된 자바스크립트부터 실행한다.

즉, 순서가 보장되지 않는다. 따라서 순서 보장이 중요한 경우에는 사용하지 않는 것이 좋다.

 

defer

자바스크립트 파싱과 실행은 HTML 파싱이 완료된 직후, 즉 DOM 생성이 완료된 직후에 진행된다.

따라서 DOM 생성이 완료된 이후 실행되어야 할 자바스크립트에 유용하다.

 

종합하면

async 는 스크립트 로드 순서에 상관없이 실행되는 경우에 적합하고,

defer는 스크립트 실행 순서가 중요할 때 적합하다.

 

 

Reference

https://www.youtube.com/watch?v=Mqh13dNI8jc&t=646s

 

https://www.youtube.com/watch?v=R23JmhbPnVo

 

모던 자바스크립트 Deep Dive

http://product.kyobobook.co.kr/detail/S000001766445?LINK=NVB&NaPm=ct%3Dlwt20us8%7Cci%3D3eeae87413d188962c2189bfc03af972cec288fd%7Ctr%3Dboksl1%7Csn%3D5342564%7Chk%3D0a86971807241e21311b2f82ad997d61e4c24ab4

 

모던 자바스크립트 Deep Dive | 이웅모 - 교보문고

모던 자바스크립트 Deep Dive | 269개의 그림과 원리를 파헤치는 설명으로 ‘자바스크립트의 기본 개념과 동작 원리’를 이해하자!웹페이지의 단순한 보조 기능을 처리하기 위한 제한적인 용도로

product.kyobobook.co.kr

 

 

https://poiemaweb.com/js-dom

 

DOM | PoiemaWeb

브라우저는 웹 문서(HTML, XML, SVG)를 로드한 후, 파싱하여 DOM(문서 객체 모델. Document Object Model)을 생성한다. 파일로 만들어져 있는 웹 문서를 브라우저에 렌더링하기 위해서는 웹 문서를 브라우저

poiemaweb.com

https://www.maeil-mail.kr/

 

매일메일 - 기술 면접 질문 구독 서비스

기술 면접 질문을 매일매일 메일로 보내드릴게요!

www.maeil-mail.kr

 

728x90

'Study > CS' 카테고리의 다른 글

SSR 과 CSR 의 차이  (0) 2024.11.27
TCP/UDP 알아보기  (1) 2024.11.12
WebSocket에 대해서 알아보기  (0) 2024.11.11