# 2023. 10.

# 10. 15.

# Critical Rendering Path 그림으로 이해하기

본 글은 실습 시나리오와 그림을 통해 실제 CRP가 어떻게 진행되고, 단계별 상태를 풀어서 이해하기 쉽도록 작성한 글이다. 잘못된 내용이 있을시 yongwon4130@gmail.com으로 문의해주길 바란다.

개요


브라우저는 Browser Engine, Network Engine, Rendering Engine, Javascript Engine 등 다양한 엔진으로 구성되어있다. 그 중 성능에 가장 많은 영향을 미치는 Rendering Engine에서 발생하는 일을 알아보고자 한다. Rendering EngineNetwork Engine으로부터 받아온 HTML 문서를 최종적으로 화면에 렌더링해주는 역할을 하는데 이 일련의 과정을 Critical Rendering Path (이하 CRP) 라고 한다. CRP의 과정은 그림 1과 같다.

그림 1. Critical Rendering Path 과정.

실습 환경 및 시나리오


브라우저에서 다음과 같은 HTML문서를 파싱한다고 가정한다. 유저는 웹사이트에 접속하여 index.html을 요청하고 이후 브라우저의 Rendering Engine이 처리하는 과정을 살펴본다.

<!-- index.html -->

<html lang="en">
  <head>
    <link rel="stylesheet" href="./style.css" />
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>

  <body>
    <header>
      <h1>CRP 이해하기</h1>
      <span>2023. 10. 13. 금요일</span>
    </header>
    <script src="./index.js"></script>
    <main>
      <h2>본문입니다.</h2>
      <img src="./image.jpg" alt="" />
      <span>부가 설명입니다.</span>
    </main>
    <footer>
      <span>Copyright 2023. ChoiYongWon inc. all rights reserved. </span>
    </footer>
  </body>
</html>
/* style.css */

body {
  font-size: 16px;
}

header {
  font-weight: bold;
}
h1 {
  font-size: 24px;
}

main {
  color: gray;
}
h2 {
  font-size: 20px;
}

footer {
  display: none;
}
// index.js

const el = document.getElementsByTagName("span")
for (let i = 0; i < el.length; i++) {
  el[i].style.color = "red"
}

HTML 파싱 DOM, CSSOM Tree 생성


HTML 파싱은 HTML 문서를 위에서 아래로 절차적으로 파싱하여DOM TreeCSSOM Tree를 만드는 과정이다. 전체 파싱 과정과 파싱 중에 index.html 코드에서 하이라이팅된 3개의 리소스 (css, script, img)를 처리하는 방식을 살펴보자.

stylesheet 리소스

Line 3

<link rel="stylesheet" href="./style.css" />
  • Line 3: link 태그를 만나면서 style.css를 서버에 요청하고, 응답을 받으면 병렬적으로 CSSOM Tree을 생성한다. 이 때 HTML 파싱은 멈추지 않고 계속 진행한다. 생성된 CSSOM Tree그림 2와 같다.
그림 2. Line 3에서 생성된 CSSOM Tree.

script 리소스

Line 14

<script src="./index.js"></script>
  • Line 14: script 태그를 만나면 HTML 파싱이 즉시 멈추고 index.js를 서버에 요청하고 JS Engine에게 실행을 위임한다. 그 후 다시 Rendering Engine이 권한을 위임 받아 HTML파싱을 계속한다.

scriptDOM TreeCSSOM Tree을 수정할 가능성이 있으므로 HTML 파싱 과정을 멈추고 실행된다. 이 때 Line 3에서 요청한 style.cssCSSOM Tree가 생성되지 않았다면, 생성될 때 까지 기다리고 script를 실행한다. index.js 의 코드는 document 객체 내의 모든 span 태그의 글자 색상을 red로 변경 의 내용을 포함한다. index.html을 살펴보면 문서 내에 총 2개의 span 태그가 있음을 알 수 있는데 (Line 12, 18), index.js가 실행되는 시점에서의 DOM Tree그림 3과 같으므로 Line 12span 태그에만 스타일이 적용된다.

그림 3. Line 14 시점의 Dom Tree.

index.js가 실행된 후 CSSOM Tree의 상태는 그림 4와 같다.

그림 4. index.js 실행 후 CSSOM Tree의 상태.

image 리소스

Line 17

<img src="./image.jpg" alt="" />
  • Line 17: img 태그를 만난다. image.jpg를 서버에 요청한다. 이 때 파싱은 stylesheet 리소스를 요청할 때와 같이 멈추지 않고 계속 진행한다.

Line 24까지 모든 파싱을 마치고 최종 생성된 DOM Tree그림 5와 같다.

그림 5. 최종 DOM Tree 모습.

앞서 설명한 HTML 파싱 과정을 waterfall 방식으로 나타내면 그림 6과 같다. cssimage는 병렬적으로 요청되지만 script 태그를 만났을때는 파싱이 잠시 중단된다. 이는 위에서 언급했듯이 cssimageDOM Tree를 생성하는데 아무런 영향을 미치지 않지만 script는 파싱 도중에 DOM Tree를 직접적으로 수정할 수 있기 때문이다. 또한 script는 파싱 중에 CSSOM Tree도 수정할 수 있으므로 script를 실행하기 전에 CSSOM Tree이 아직 생성되지 않았다면 완료될때까지 기다린다.

그림 6. HTML 파싱 Waterfall.

HTML 파싱이 완료되고 Dom Tree가 생성된 시점에서 document 객체에서 DomContentLoaded 이벤트가 발생한다. 이 이벤트는 그림 7에서 파란색 실선이다. index.js 스크립트가 모두 실행되고 호출되는 시점으로, 이는 Line 14의 스크립트를 동기적으로 실행하고 Line 24(끝)까지 파싱을 완료하고 Dom Tree를 생성한 시점이다. 빨간색 실선은 나머지 과정(HTML 파싱 ~ paint)이 모두 완료된 후 호출되는 Load 이벤트를 나타낸다.

그림 7. Chrome Network 탭에서의 HTML 파싱 과정.

Render Tree


Render Tree는 앞서 생성된 DOM TreeCSSOM Tree를 적용시켜 생성된 새로운 Tree이다. Render Tree는 실제 화면에 표시될 노드들로만 구성되어있다. 그래서 head 태그는 포함되지 않으며 body 태그 내의 내용들로 구성되어있다. 실제 그림 4그림 5를 합쳐서 구성된 Render Tree그림 8과 같다.

그림 8. DOM Tree와 CSSOM이 합쳐진 Render Tree.

Render Tree는 화면에 표시되는 모든 요소를 포함하며, 요소에 적용되는 스타일 정보를 나타낸다. 이 Render Tree를 구성하는 과정에서 각 요소의 스타일은 하향식으로 종속적으로 적용된다. 이 때, 자식 스타일과 부모 스타일이 겹치게되면 더 깊게 적용된 스타일에 우선순위를 두며 상속받은 스타일은 무시해버린다 (그림 8 참조). Render Tree는 DOM 요소의 스타일 정보와 레이아웃 정보를 담고 있지만, 이 정보는 요소가 화면에 어떤 위치에 렌더링되어야 하는지 직접적으로 나타내지는 않는다. 요소의 실제 위치는 레이아웃(Layout) 단계에서 결정된다.

Layout(Reflow)


Layout 단계에서는 viewport를 기준으로 요소의 크기, 위치, 여백, 패딩 등이 계산되어 요소가 실제 화면에 배치될 위치(좌표)가 정해진다. 그래서 Layout을 일으키는 기준인 브라우저의 창 크기나 CSS Box Model을 수정하면 화면에 그려지는 좌표값이 달라지기 때문에 Layout 과정을 다시 계산하게 된다. 그림 9는 실제 Box Model과 이미지의 width를 각각 20%, 30%로 설정한 다음 브라우저 창 크기를 반복적으로 리사이즈하여 실제로 Layout 과정이 이루어지는지 측정해보았다.

그림 9. Layout 성능 측정.

그림 10의 이벤트 로그를 확인해보면 Viewport 크기가 변경될 때마다 Layout 과정이 다시 이루어지면서 요소의 좌표값을 계산해주는 모습을 확인할 수 있다.

그림 10. Layout 성능 측정 결과.

이렇게 화면에 보이는 요소 각각이 어디에 어떻게 위치할 지를 정해주는 과정을 Webkit에서는 layout으로, Gecko에서는 reflow로 부르고 있다.

Paint


Paint 단계는 Layout 단계에서 변환된 실제 좌표 값을 화면에 그려주는 역할을 한다. Layout 단계에서는 요소의 크기와 위치에 기준을 두었다면 Paint를 일으키는 기준은 요소의 색상이나 배경이 된다. 그림 11은 Box의 테두리 색상을 100ms 간격으로 변경하게 두고 실제로 Paint 과정이 이루어지는지 측정해보았다.

그림 11. Paint 성능 측정.

그림 12의이벤트 로그를 확인해보면 테두리 색상만 변경되었으므로 Layout 과정없이 Paint 단계부터 다시 실행되는걸 확인할 수 있다.

그림 12. Paint 성능 측정 결과.

Reference