Chrome Dev Summit 2018 is happening now and streaming live on YouTube. Watch now.

렌더링 트리 생성, 레이아웃 및 페인트

CSSOM 및 DOM 트리는 결합하여 렌더링 트리를 형성합니다. 이 렌더링 트리는 표시되는 각 요소의 레이아웃을 계산하는 데 사용되고 픽셀을 화면에 렌더링하는 페인트 프로세스에 대한 입력으로 처리됩니다. 최적의 렌더링 성능을 얻기 위해서는 이러한 단계 각각을 최적화하는 것이 중요합니다.

객체 모델을 생성하는 방법을 설명한 이전 섹션에서 우리는 HTML 및 CSS 입력을 기반으로 DOM 및 CSSOM 트리를 빌드했습니다. 하지만, 이들 모두 문서의 각기 다른 측면을 캡처하는 서로 독립적인 객체입니다. 하나는 콘텐츠를 설명하고, 다른 하나는 문서에 적용되어야 하는 스타일 규칙을 설명합니다. 이 두 가지를 병합하여 브라우저가 화면에 픽셀을 렌더링하도록 하려면 어떻게 해야 할까요?

TL;DR

  • DOM 및 CSSOM 트리는 결합되어 렌더링 트리를 형성합니다.
  • 렌더링 트리에는 페이지를 렌더링하는 데 필요한 노드만 포함됩니다.
  • 레이아웃은 각 객체의 정확한 위치 및 크기를 계산합니다.
  • 마지막 단계는 최종 렌더링 트리에서 수행되는 페인트이며, 픽셀을 화면에 렌더링합니다.

먼저, 브라우저가 DOM 및 CSSOM을 '렌더링 트리'에 결합합니다. 이 트리는 페이지에 표시되는 모든 DOM 콘텐츠와 각 노드에 대한 모든 CSSOM 스타일 정보를 캡처합니다.

DOM 및 CSSOM은 결합되어 렌더링 트리를 생성합니다.

렌더링 트리를 생성하려면 브라우저가 대략적으로 다음 작업을 수행합니다.

  1. DOM 트리의 루트에서 시작하여 표시되는 노드 각각을 트래버스합니다.

    • 일부 노드는 표시되지 않으며(예: 스크립트 태그, 메타 태그 등), 렌더링된 출력에 반영되지 않으므로 생략됩니다.
    • 일부 노드는 CSS를 통해 숨겨지며 렌더링 트리에서도 생략됩니다. 예를 들어,---위의 예시에서---span 노드의 경우 'display: none' 속성을 설정하는 명시적 규칙이 있기 때문에 렌더링 트리에서 누락됩니다.
  2. 표시된 각 노드에 대해 적절하게 일치하는 CSSOM 규칙을 찾아 적용합니다.

  3. 표시된 노드를 콘텐츠 및 계산된 스타일과 함께 내보냅니다.

참고: 간단한 여담으로, visibility: hiddendisplay: none과 다릅니다. 전자는 요소를 보이지 않게 만들지만, 이 요소는 여전히 레이아웃에서 공간을 차지합니다(즉, 비어 있는 상자로 렌더링됨). 반면, 후자(display: none)는 요소가 보이지 않으며 레이아웃에 포함되지도 않도록 렌더링 트리에서 요소를 완전히 제거합니다.

최종 출력은 화면에 표시되는 모든 노드의 콘텐츠 및 스타일 정보를 모두 포함하는 렌더링 트리입니다. 렌더링 트리가 생성되었으므로 '레이아웃' 단계로 진행할 수 있습니다.

지금까지 표시할 노드와 해당 노드의 계산된 스타일을 계산했습니다. 하지만 기기의 뷰포트 내에서 이러한 노드의 정확한 위치와 크기를 계산하지는 않았습니다.---이것이 바로 '레이아웃' 단계이며, 경우에 따라 '리플로우'라고도 합니다.

페이지에서 각 객체의 정확한 크기와 위치를 파악하기 위해 브라우저는 렌더링 트리의 루트에서 시작하여 렌더링 트리를 트래버스합니다. 간단한 실습 예시를 살펴보도록 하겠습니다.

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Critial Path: Hello world!</title>
  </head>
  <body>
    <div style="width: 50%">
      <div style="width: 50%">Hello world!</div>
    </div>
  </body>
</html>

체험해 보기

위 페이지의 본문에는 두 가지 중첩된 div가 포함되어 있습니다. 첫 번째(상위) div는 노드의 표시 크기를 뷰포트 너비의 50%로 설정하며,---상위 div에 포함된---두 번째 div는 해당 너비를 상위 항목 너비의 50%(즉, 뷰포트 너비의 25%)로 설정합니다.

레이아웃 정보 계산

레이아웃 프로세스에서는 뷰포트 내에서 각 요소의 정확한 위치와 크기를 정확하게 캡처하는 '상자 모델'이 출력됩니다. 모든 상대적인 측정값은 화면에서 절대적인 픽셀로 변환됩니다.

마지막으로, 이제 표시되는 노드와 해당 노드의 계산된 스타일 및 기하학적 형태에 대해 파악했으므로, 렌더링 트리의 각 노드를 화면의 실제 픽셀로 변환하는 마지막 단계로 이러한 정보를 전달할 수 있습니다. 이 단계를 흔히 '페인팅' 또는 '래스터화'라고 합니다.

이 경우 브라우저가 처리해야 할 작업이 상당히 많으므로 시간이 약간 걸릴 수 있습니다. 그러나 Chrome DevTools는 위에 설명된 세 단계 모두에 대해 몇 가지 정보를 제공할 수 있습니다. 원래 'hello world' 예시의 레이아웃 단계를 검토해 보도록 하겠습니다.

DevTools에서 레이아웃 측정

  • 'Layout' 이벤트는 타임라인에서 렌더링 트리 생성, 위치 및 크기 계산을 캡처합니다.
  • 레이아웃이 완료될 때 브라우저가 'Paint Setup' 및 'Paint' 이벤트를 발생시킵니다. 이러한 작업은 렌더링 트리를 화면의 픽셀로 변환합니다.

렌더링 트리 생성, 레이아웃 및 페인트 작업을 수행하는 데 필요한 시간은 문서의 크기, 적용된 스타일 및 실행 중인 기기에 따라 달라집니다. 즉, 문서가 클수록 브라우저가 수행해야 하는 작업도 더 많아지며, 스타일이 복잡할수록 페인팅에 걸리는 시간도 늘어납니다. 예를 들어, 단색은 페인트하는 데 시간과 작업이 적게 필요한 반면, 그림자 효과는 계산하고 렌더링하는 데 시간과 작업이 더 필요합니다.

페이지가 드디어 뷰포트에 표시됩니다.

렌더링된 Hello World 페이지

다음은 브라우저의 단계를 빠르게 되짚어 보겠습니다.

  1. HTML 마크업을 처리하고 DOM 트리를 빌드합니다.
  2. CSS 마크업을 처리하고 CSSOM 트리를 빌드합니다.
  3. DOM 및 CSSOM을 결합하여 렌더링 트리를 형성합니다.
  4. 렌더링 트리에서 레이아웃을 실행하여 각 노드의 기하학적 형태를 계산합니다.
  5. 개별 노드를 화면에 페인트합니다.

여기에 표시된 데모 페이지는 간단해 보일 수 있지만, 이 페이지에도 꽤 많은 작업이 필요합니다. DOM 또는 CSSOM이 수정된 경우, 화면에 다시 렌더링할 필요가 있는 픽셀을 파악하려면 이 프로세스를 다시 반복해야 합니다.

주요 렌더링 경로를 최적화하는 작업 은 위 단계에서 1단계~5단계를 수행할 때 걸린 총 시간을 최소화하는 프로세스입니다. 이렇게 하면 콘텐츠를 가능한 한 빨리 화면에 렌더링할 수 있으며, 초기 렌더링 후 화면 업데이트 사이의 시간을 줄여 줍니다. 따라서 대화형 콘텐츠의 새로고침 속도를 높일 수 있습니다.