Pandaman Blog

브라우저의 Reflow 와 Repaint 본문

Front end/Browser

브라우저의 Reflow 와 Repaint

oyg0420 2020. 3. 6. 21:14

 

우리는 reflow와 repaint개념에 대해 알기 전에 브라우저의 구성요소와 동작 방식을 이해해야 한다.

브라우저의 구조

아래는 브라우저의 구조를 시각적으로 나타낸 이미지이다.

브라우저의 구조

  • User Interface는 브라우저의 주소창, 뒤로 가기, 즐겨찾기 등  웹페이지를 조작할 수 있는 인터페이스이다.
  • Browser Engine은 User Interface와 Rendering Engine 사이의 동작을 제어한다고 한다.
  • Rendering Engine은 여기서 가장 중요하게 생각하는 부분이다. 브라우저에서 보이는 화면을 표시하도록 하는 엔진이다.
  • 자바스크립트 해석기는 자바스크립트를 해석하고 실행한다.
  • 통신은 네트워크 호출에 사용된다.
  • UI Backend는 브라우저 창 같은 기본적인 장치를 그린다.
  • Date Persistence(자료 저장소)는 Local Storage, Indexed DB, 쿠키 등 브라우저 메모리를 활용하여 저장하는 영역이다.

브라우저의 동작 방식

브라우저 엔진 중에서 이번 주제에서 공부해야 할 엔진은 렌더링 엔진이다. 위에서 렌더링 엔진이 브라우저에 화면을 표시한다고 말했다.
렌더링 엔진은 통신으로부터 요청한 문서의 내용을 얻는 것으로 시작한다. 한마디로 우리가 주소창에 주소를 입력했을 때 얻은 HTML 문서를 파싱 하여 우리에게 보여주는 것이다.

브라우저 동작 방식(WebKit)

위의 그림은 렌더링 엔진이 어떻게 화면을 표시하는지에 대한 그림이다.

렌더링 엔진은 HTML 문서를 파싱 한다. 그리고 DOM tree를 구축한다. 다른 한쪽에서는 CSSOM tree를 구축한다. DOM tree와 CSSOM 결합되어 렌더 트리를 구성한다. 그리고 각각의 노드가 화면에 정확한 위치에 표시되도록 Layout(Webkit) 또는 Reflow(Gecko)를 시작한다. 그리고 브라우저는 paint(화면에 그리기)를 실시한다.

DOM Tree와 CSSOM Tree 구축

위에 동작 방식중 HTML 문서가 어떻게 파싱 되어 DOM tree, CSSOM tree를 구축하는지 알아보자.

DOM Tree 구축과정

첫 번째로 브라우저는 HTML의 원시 바이트를 해당 파일에 지정된 인코딩에 따라 문자로 변환한다.
두 번째로 브라우저가 변환된 문자를 W3C HTML5 표준에 지정된 고유 토큰으로 변환한다. 꺽쇠로 묶인 문자열로 말이다. 이 과정을 토큰화라고 한다.
세 번째로 Lexing(낱말 분석)을 실시한다. Lexing은 규칙을 정의한 속성에 토큰을 값으로 지정한다. 즉 객체로 변환한다.
예) { StartTag: html }, { StartTag: span }, { EndTag: span },...
네 번째로 DOM을 생성한다. HTML 마크업이 여러 태그 간의 관계(html > head > body...)를 정의하기 때문에 생성된 객체는 트리 구조로 연결된다.

위 단계의 최종 출력본은 DOM(Document Object Model)이다.

DOM(Document Object Model)

이제 CSSOM에 대해 알아보자.

브라우저는 외부 css 스타일시트인 style.css를 참조하는 문서의 해드 섹션에 링크 태그를 통해, 리소스를 요청한다.
요청 결과로 다음과 같은 결과(예시)를 반환한다.

body { font-size: 16px }
p { font-weight: bold }
span { color: red }
p span { display: none }
img { float: right }

HTML과 마찬가지로 CSS를 브라우저가 이해하고 처리할 수 있도록 동일한 과정으로 변환한다.
그 결과 CSS Object Model(CSSOM)이라는 트리 구조가 생성된다.

CSS Object Model(CSSOM)

Attachment(Render Tree 구축)

렌더 트리는 DOM과 CSSOM의 결합으로 생성된다. 렌더 트리는 페이지에 표시되는 모든 DOM 콘텐츠와 각 노드에 대한 모든 CSSOM 스타일 정보를 담고 있다.
DOM Tree에 있는 모든 노드가 Render Tree로 추가되는 것이 아니다.
비시각적 DOM의 요소들은(meta, head 등) 렌더 트리에 추가되지 않는다. 또한, display 속성의 'none'값이 할당된 요소는 렌더 트리에 추가되지 않는다. 위에서 렌더 트리는 페이지에 표시되는..이라고 설명한 이유이다.

아래는 DOM Tree와 CSSOM Tree가 결합 후의 Render Tree를 표현한 그림이다.

 

Render Tree

Layout(Reflow)

Layout(reflow) 단계에서는 브라우저의 Viewport 내에서 생성된 Render Tree의 각각의 노드들의 위치와 크기를 계산한다.
즉, 생성된 Render Tree 노드들이 가지고 있는 스타일과 속성에 따라서 브라우저 화면의 어느 위치에 어느 크기로 출력될지 계산하는 단계이다. 이를 통해 %와 같이 상대적인 위치, 크기 속성은 실제 화면에 그려지는 pixel단위로 변환된다.

Paint

Layout 단계를 통해 위치, 크기 속성이 pixel단위로 변환되면, 즉, 레이아웃이 완료되면 브라우저는 'Paint Setup' 및 'Paint' 이벤트를 발생시킨다. 이 이벤트를 통해 렌더링 트리를 화면의 픽셀로 변환한다. 이 과정을 거친 후 브라우저 화면에 UI가 나타나게 된다.

Reflow란?

어떠한 액션이나 이벤트에 의해 DOM 요소의 크기나 위치 등을 변경하면 해당 노드의 하위 노드와 상위의 노드들을 포함하여 Layout 단계를 다시 수행하게 된다. 즉 변경하려는 특정 요소의 위치와 크기뿐만 아니라 연관된 요소들의 위치와 크기도 재계산을 하기 때문에 브라우저의 퍼포먼스를 저하시키는 요인이다.

해당 코드는 요소를 클릭했을 때 reflow가 발생하는지 확인해보기 위한 코드이다.

<html>
<head>
<style>
#test {
  width: 100px;
  background-color: blue;
  height: 50px;
}
</style>
</head>
<body>
<div id="test"/>
<script>
  const test = document.getElementById("test");
  function clickEventHandler(e) {
    e.target.style.width = "150px";
  }
  test.addEventListener('click', clickEventHandler);
</script>
</body>
</html>

첫 번째로 위의 코드를 복사하고 html 파일을 만들자.
해당 코드가 있는 html 파일을 크롬 브라우저를 통해 열고 크롬의 Dev Tool의 Performance 탭을 클릭해보자.

start profile(새로고침 아이콘)를 클릭한다.

Chrome Dev Tool의 Performance 제어 아이콘

그리고 Event Log를 확인해보면 Layout은 한 번만 발생한 것을 확인할 수 있다.

Reflow가 발생하지 않은 경우

다시 start profile를 클릭하고 해당 요소를 클릭해보자. 그리고 Event Log를 확인 Layout이 한번 더 발생한 것을 확인할 수 있다.

Reflow가 발생한 경우

이렇게 요소의 변화가 생기면 reflow가 발생하는 것을 확인했고, 크롬 Dev Tool을 사용하여 시각적으로도 확인할 수 있다. 참고로 Call Tree 탭을 클릭하여 Layout의 총시간을 확인할 수 있다.

Repaint란?

Render Tree를 다시 화면에 그려주는 과정이다. 즉 paint 과정이 다시 수행되는 것이다.
위에서 설명한 Reflow가 발생하면 변경된 Render Tree를 화면에 다시 그려줘야 한다. 이때, Repaint가 발생한다.
또한 레이아웃에 영향을 주지 않는 color, background-color 등과 같은 스타일 속성을 변경했을 때 발생한다. Repaint 역시 Reflow와 같이 연산이 필요하기 때문에 브라우저의 퍼포먼스에 영향을 준다.

 

아래의 코드는 요소에 클릭 이벤트가 발생하면 background-color 속성을 변경하는 예제이다.

<html>
<head>
<style>
#test {
  width: 100px;
  background-color: blue;
  height: 50px;
}
</style>
</head>
<body>
<div id="test"/>
<script>
  const test = document.getElementById("test");
  function clickEventHandler(e) {
    e.target.style.backgroundColor = "yellow";
  }
  test.addEventListener('click', clickEventHandler);
</script>
</body>
</html>

reflow와 동일하게 Chrome Dev Tool의 Performance를 활용해보자. start profile(새로고침 아이콘)를 클릭한다. 그리고 Event Log를 확인해보자. Paint는 한번 발생한 것을 확인할 수 있다.

Repaint가 발생하지 않은 경우

다시 start profile(새로고침 아이콘)를 클릭한다. 요소를 클릭하여 색상을 변경한다. 그리고 Event Log를 확인해보자. Paint는 두 번 발생한 것을 확인할 수 있다.

Repaint가 발생한 경우

브라우저의 성능을 개선시키기 위한 방법

브라우저의 성능을 개선시키는 방법을 알기 앞서 무엇이 Repaint 와 Reflow를 발생시키는 확인해보자.

1) 브라우저 창 크기 변화

2) 폰트 크기의 변화

3) 스크롤 하는 경우

4) 클래스 속성 조작

 DOM의 변경이 있는 경우 reflow와 repaint 을 발생시킨다.

 

아래는 우리가 Reflow를 최소화하는 방법이라고 한다.

1. DOM Tree의 Depth를 최소화한다.
DOM이 Tree의 Depth가 얕을수록 Reflow가 발생했을 때, 비용을 절감할 수 있다. 그 이유는 변화된 요소에 의해 영향을 받은 요소들이 적어 그 만큼 Layout단계에서 소비되는 연산이 줄어든 것을 의미한다.

2. javascript를 통해 스타일 변화를 주어야 할 경우, 가급적 한 번에 처리한다.

e.target.style.width = "150px"; // reflow 발생
e.target.style.height = "300px"; // reflow 발생
e.target.style.position = "relative"; // reflow 발생

따라서 위와 같은 방법보다 요소에 한 번만 접근하여 스타일을 변경하는 것이 위의 방법보다 적게 Reflow와 Repaint를 발생시킨다.

e.target.style = "width: 150px; height: 300px; position: relative; display: inline-block; overflow: hidden; font-size: 40px; color: #fff;"; // reflow 발생

3. CSS 하위 선택자는 필요한 만큼만 정리하는 것이 비용이 절감된다.
CSS의 Rule 매칭은 우측에서 좌측으로 흐른다. 더 이상 요소와 매칭 시킬 Rule이 없다면 다음 Rule에 해당하는 요소를 찾는다. 결국 Rule에 해당하는 요소를 찾는 과정이 비용이 되는 것이다. 따라서, CSS Rule이 적다면, Reflow는 더 빠르게 진행된다.

예제를 살펴보자.

.parent .child-1 li .child-2 .child-3 {display:block;width:100px;height:30px;}

총 5단계를 걸쳐 요소와 스타일을 매칭 시킨다.

.parent .child-1 .child-3 {display:block;width:100px;height:30px;}

3단계를 걸쳐 요소와 스타일을 매칭 시킨다.

첫 번째 예제와 같이 필요 이상의 Rule을 지정하게 되는 경우 퍼포먼스가 떨어질 수 있다. 물론 위의 예제는 큰 차이는 없겠지만, 코드량이 많은 경우 퍼포먼스에 영향을 미칠 것이다. 따라서, 요소와의 매칭에 필요한 Rule 선언이 필요하다.

 

4. 애니메이션이 있는 요소는 position: absolute 또는 position: fixed로 지정하는 것이 좋다.

애니메이션은 요소의 크기 또는 위치가 변경되는것을 의미하는데, 요소가 변경될 때마다 주위 요소들은 영향을 받게 위치나 크기가 변경될 수 있다. 따라서 reflow가 발생되고 변경된 요소에 의한 나머지 요소들도 다시 위치를 재계산하게 되어 결국 reflow(layout) 시간은 늘어나게 된다.

애니메이션이 들어간 요소의 display 속성을 position: absolute 또는 position: fixed로 지정하여 다른 요소에 영향이 없도록 하는 것이 reflow(layout) 단계에서 연산시간이 줄어들 수 있다.

 

사실 위에서 개선방법 말고도 다양한 방법들이 존재한다. 브라우저의 최적화를 위해서는 DOM 변경이 최소화되어야 하고, 변경되더라도 다른 DOM 요소에 영향을 최소화해야 한다. 최근 웹 애플리케이션은 굉장히 복잡하다. 사용자들의 편의를 돕기 위한 :hover 속성이나, 클릭 이벤트가 발생했을 때 DOM을 변화시키는 등의 다양한 기능들이 포함되어 있다. 따라서, Reflow와 Repaint가 자주 발생되고, 브라우저는 많은 연산을 해야 한다.

Virtual DOM

Virtual DOM은 말 그대로 가상의 DOM이다. 왜 Virtual DOM은 무엇을 하는 녀석이고 왜 필요한 걸까?

아래의 그림을 보자.

가상 DOM의 동장

위 이미지는 Virtual DOM(In-memory DOM)의 역할을 보여준다.

가상 DOM은 실제 DOM의 복제본을 메모리에 저장한다. DOM을 수정하면 먼저 변경 사항을 메모리 내에 있는 DOM에 적용한다.
실제 DOM과 비교연산(Diffing) 하여 실제로 변경된 내용을 일괄 처리하여 변경사항을 적용(Patch)한다. 즉, 최종적인 변화를 하나로 묶어 실제 DOM에 적용하는 것이다. 따라서 연산 횟수를 줄일 수 있다.

 

그렇다면 Virtual DOM은 DOM 보다 빠르다 할 수 있을까? 무조건  Virtual DOM이 빠르다고 할 순 없다. 정보 제공만 하는 웹 페이지라면 일반 DOM이 더 성능이 좋다. 하지만 대규모 SPA(Single Page Application)의 웹 페이지에서는 DOM의 조작이 많이 발생하는데, 이 경우는 Virtual DOM을 사용함으로써 브라우저의 연산의 양을 줄여 성능을 개선할 수 있다.

 

정적인 웹을 구축한다면, DOM을 사용하는것이 좋고, 규모가 큰 동적인 웹을 사용한다면 Reflow와 Repaint 연산 리소스를 최소화하는 Virtual DOM을 사용하는 것이 바람직하다고 생각한다.

 

참조
https://developers.google.com/web/fundamentals/performance/critical-rendering-path/constructing-the-object-model?hl=ko
https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction?hl=ko
https://d2.naver.com/helloworld/59361

 

파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음

 

'Front end > Browser' 카테고리의 다른 글

웹 성능측정 방법  (0) 2021.11.15
Comments