꿈꾸는 개발자가 되기 위해
브라우저 렌더링 과정에 대해 본문
모바일 프로젝트에 투입되기 전
간단한 자기 학습을 할 시간을 가지게 되었다.
그중 하나인 브라우저 렌더링 과정에 대해 알아보고자 한다.
렌더링 과정을 알아보기 전에, 우선 브라우저의 구조를 알아보면 좋을 것 같다.
-
사용자 인터페이스
-
주소 표시줄, 이전/다음 버튼, 북마크 메뉴 등. 요청한 페이지를 보여주는 창을 제외한 나머지 모든 부분이다.
-
-
브라우저 엔진
-
사용자 인터페이스와 렌더링 엔진 사이의 동작을 제어.
-
-
렌더링 엔진
-
요청한 콘텐츠를 표시. 예를 들어 HTML을 요청하면 HTML과 CSS를 파싱하여 화면에 표시함.
-
-
통신
-
HTTP 요청과 같은 네트워크 호출에 사용됨. 이것은 플랫폼 독립적인 인터페이스이고 각 플랫폼 하부에서 실행됨.
-
-
UI 백엔드
-
콤보 박스와 창 같은 기본적인 장치를 그림. 플랫폼에서 명시하지 않은 일반적인 인터페이스로서, OS 사용자 인터페이스 체계를 사용.
-
-
자바스크립트 해석기
-
자바스크립트 코드를 해석하고 실행.
-
-
자료 저장소
여기서, 렌더링 엔진 이 앞으로 다룰 브라우저 렌더링 과정을 담당하게 된다.
브라우저 렌더링 과정은 크게 5가지의 순서로 나눌 수 있다.
-
HTML 파일을 로드하여 DOM (Document Object Model) Tree 를 만든다.
-
CSS 파일을 로드하여 CSSOM (CSS Obejct Model) Tree 를 만든다. (스타일 구조체라고도 함)
-
DOM Tree와 CSSOM Tree를 서로 결합 (attachment) 하여 Render Tree 를 만든다.
-
Render Tree 에서 레이아웃 작업을 통해 브라우저에 표시할 위치와 크기를 계산한다.
-
계산된 Render Tree 의 개별 노드를 페인팅 작업을 통해 브라우저에 그린다.
1) DOM (Document Object Model) Tree 생성
브라우저는 HTML 태그를 파싱해 DOM Tree 를 구성한다.
DOM은 데이터의 표현식으로 모든 HTML 태그에는 그에 상응하는 노드가 있으며, 태그 사이에는 텍스트 데이터가 포함될 수 있다. 이 또한 텍스트 노드의 표현식이다.
<html>
<head>
<title>테스트</title>
</head>
<body>
<p>hello world1</p>
<div>
<img src="example.png" />
</div>
</body>
</html>
각 태그는 태그 데이터의 표현식인 DOM 요소로 1:1로 대응해 표현되며, DOM 요소 노드는 트리 형태로 구성된다. 이를 DOM 트리라고 한다.
2) CSS (CSS Object Model) Tree (스타일 구조체) 생성
스타일 정보(스타일시트 파일이나 요소에 지정된 인라인 정보 등)를 통해 스타일 구조체(Style Struct)를 생성한다.
스타일 정보는 단계적으로 처리되며, 가장 마지막 단계의 스타일 정보가 이전 스타일보다 우선으로 적용된다.
스타일 정보는 다음과 같이 3단계로 나누어 처리된다.
- 브라우저 자체에 포함된 기본 스타일 정보(User Agent 스타일시트)
- 사용자 정의 스타일(외부 파일 또는 내부 정의 스타일)
- HTML 태그에 style 속성을 사용해 기술되는 인라인 스타일 정보
<!-- 외부 파일 -->
<link rel="stylesheet" type="text/css" href="/css/main.css">
<!-- 내부 정의 스타일 -->
<style type="text/css">
body { background-color:#ffffff;
background-image:none;
background-reapeat:repeat; }
</style>
한편, 이론적으로는 CSS는 DOM Tree 를 변경하지 않기 때문에, HTML 문서 파싱을 기다릴 필요가 없다.
그러나, 문서를 파싱하는 동안 CSS 를 요청하게되면 문제가 발생하는데
- 파싱중인 CSS 가 있으면 모든 스크립트를 중단
- 로드되지 않은 CSS 에서 문제점이 존재하는 속성이 있을 때 중단.
위와 같은 상황은 빈번하게 발생한다.
3) Render Tree 생성
생성된 DOM 트리와 CSSOM 트리를 가지고 Render Tree (이하 렌더 트리) 를 만든다.
렌더 트리는 DOM 트리와는 다르게 각 노드에 스타일 정보가 설정돼 있고 화면에 표현되는 노드로 구성된다. 어떤 노드는 스타일이 'display:none'으로 설정되어 있으면 해당 노드는 렌더 트리에 포함되지 않는다.
또한, DOM 트리와 렌더 트리의 노드는 서로 1:1로 대응되지 않는다.
DOM 트리의 구성원 가운데 일부인 노드들은 화면에 표시되지 않는 노드이므로 렌더 트리에 포함되지 않는다.
DOM 트리의 단일 구성원이지만 렌더 트리에서는 여러 개의 노드로 구성되는 경우도 있는데, <br> 태그로 인해 줄이 바뀌거나 노드 내에서 자연스럽게 줄이 바뀐 경우 등 단일 텍스트 노드가 여러 줄로 출력되는 경우가 여기에 해당한다..
따라서, 내용을 종합하면 다음과 같다.
- 일부 노드는 표시되지 않으며 (ex) <script>, <meta> 등) 렌더링된 출력에 반영되지 않으므로 생략된다.
- 일부 노드는 CSS를 통해 숨겨지며 렌더링 트리에서도 생략된다.
4) 레이아웃 처리
렌더 트리의 각 노드의 크기가 계산되고 문서에서 정확한 위치에 배치되도록 위치를 계산하는 작업을 레이아웃 이라고 한다. 이 과정은 CSS 비주얼 렌더링 모델(CSS Visual Rendering Model)에 의해 제어되며, 루트에서 하위 노드로 반복되며 진행된다.
또한 브라우저는 레이아웃 계산을 출력되는 화면의 해상도보다 높은 해상도로 처리하는데, 사용자가 화면을 확대 또는 축소했을 경우를 대비해 추가적인 계산없이 원본 크기 상태의 픽셀 좌푯값과 매핑해 배율에 상관없이 올바르게 배치되게 한다.
5) 페인트 (Paint)
렌더 트리를 이용하여 표시될 요소들의 위치를 계산하였으므로 이 데이터를 가지고 실제 화면에 그려주는데(렌더링 트리의 각 노드를 화면의 실제 픽셀로 변환하는 마지막 단계로 이러한 정보를 전달할 수 있다.), 이 작업을 페인트 라고 한다.
렌더 트리 생성, 레이아웃 및 페인트 작업을 수행하는 데 필요한 시간은 문서의 크기, 적용된 스타일 및 실행 중인 기기에 따라 달라진다. 즉, 문서가 클수록 브라우저가 수행해야 하는 작업도 더 많아지며, 스타일이 복잡할수록 페인팅에 걸리는 시간도 늘어난다. 예를 들어, 단색은 페인트하는 데 시간과 작업이 적게 필요한 반면, 그림자 효과는 계산하고 렌더링하는 데 시간과 작업이 더 필요하다.
아래는 위에서 설명한 렌더링의 각 단계에 따라 렌더링 엔진인 웹킷(Safari, Chrome 등)과 게코(파이어폭스 등)에서 처리되는 과정을 그림으로 표현한 것이다. 모든 과정은 점진적으로 처리된다.
최초 페이지가 로딩되면 지금까지 설명한 렌더링 작업이 진행된다.
이후 렌더링이 모두 완료된 상태에서 사용자의 인터랙션 또는 해당 페이지의 기능에 따라 화면의 일부 영역에 변경 요인이 발생한다. (ex) 레이어가 전환되거나 AJAX를 통해 새로운 데이터를 받아와서 페이지에 추가할 때 요소의 스타일이 변경되는 등)
이러한 작업이 발생하면 구성되어 있는 렌더 트리가 변경되어야 하며 리플로 또는 리페인트가 발생한다.
6) 리플로 (reflow)
변경(일부 또는 전체)이 필요한 렌더 트리에 대한 유효성 확인 작업과 함께 노드의 크기와 위치를 다시 계산한다. 이 과정을 리플로(Reflow) 라고 부른다. 좀 더 정확하게는 노드의 크기 또는 위치가 바뀌어 현재 레이아웃에 영향을 미쳐 배치를 다시 해야 할 때 리플로가 발생한다.
특정 요소에 리플로가 발생하면 요소의 DOM 구조에 따라 자식 요소와 부모 요소 역시 다시 계산될 수 있으며, 경우에 따라서는 문서 전체에 리플로가 발생할 수도 있다.
<body>
<div id="gnb_more" class="more_skin">
<p><strong>검색 중심</strong>의 메인화면으로 더 가볍고 빠르게 검색 결과를 확인할 수 있습니다.<p>
<h3 class="blind">주요 서비스</h3>
<ui class="main">
<li class="fix_wt"><a href="http://www.naver.com/">네이버</a></li>
<li><a href="http://me.naver.com/">me</a></li>
<li><a href="http://mail.naver.com/">메일</a></li>
<li><a href="http://section.cafe.naver.com/">카페</a></li>
<li><a href="http://blog.naver.com/">블로그</a></li>
</ui>
</div>
</body>
위의 구조에서 <p>....</p> 영역에 리플로가 발생하면 자식 노드인 <strong>...</strong> 또한 당연히 리플로 대상이 된다. 이에 더해 부모 노드인 <div id="gnb_more">, 형제 노드인 <h3>, <ul>까지도 리플로가 발생해 결과적으로는 문서 전체에 리플로가 발생한다.
7) 리페인트 (repaint)
변경 영역의 결과를 표현하기 위해 화면이 업데이트되는 것을 의미한다(리플로가 발생하거나 배경색 변경 등의 단순한 스타일 변경과 같은 작업이 발생하는 경우 등). 간단하게는 화면을 변경해야 할 때 발생한다고 생각하면 되는데, 이러한 작업을 리페인트(Repaint) 또는 리드로드(Redraw)라고 한다.
리플로와 리페인트 모두 처리 비용이 발생하지만 리페인트보다 리플로의 비용이 훨씬 높다. 리플로는 변경 범위에 따라 전체 페이지의 레이아웃을 변경해야 할 수도 있기 때문이다. 어느 경우든 리플로와 리페인트 때문에 UI의 화면 표현이 느려져 사용자 경험에 영향을 줄 수 있으므로 코드를 작성할 때 이를 최소화해야 한다.
발생 요인
현재 구성된 렌더 트리의 변경을 가져오는 작업이 실행되면 작업의 종류에 따라 리플로 또는 리페인트가 발생하는데, 주요 변경 요인은 다음과 같다.
- DOM 노드의 변경 : 추가, 제거 등 업데이트
- DOM 노드의 노출 속성을 통한 변경 : display:none; 은 리플로와 리페인트를 발생시키지만 비슷한 속성인 visibility:hidden; 은 요소가 차지한 영역을 유지해 레이아웃에 영향을 주지 않으므로 리페인트만 발생시킨다.
- 스크립트 애니메이션 : 애니메이션은 DOM 노드의 이동과 스타일 변경이 짧은 시간 내에 수차례 반복해 발생되는 작업이다스타일 : 새로운 스타일시트의 추가 등을 통한 스타일 정보 변경 또는 기존 스타일 규칙의 변경
- 사용자의 액션 : 브라우저 크기 조절, 글꼴 크기 변경 등
출처
https://developers.google.com/web/fundamentals/performance/critical-rendering-path?hl=ko