저번에 브라우저 렌더링 과정에 대해 공부해봤다
브라우저는 어떻게 렌더링 되는가
그럼 이제Javscript
로DOM
을 조작했을 때 다시 렌더링 되는 과정인Repaint
와Repaint
에 대해 공부해보자
Render Tree
를 빠르게 초기에 구성 하는 법기본적으로 내가 제공한 HTML , CSS
들을 User Interface
에 렌더링 시킨다는 것은
파싱하여 제작한 DOM
, CSSOM
을 이용해 Render Tree
를 만들고 Render Tree
들을
정의된 위치 및 크기 속성을 이용해 노드들이 위치할 Layout
을 계산하고
정의된 픽셀 단위의 속성을 이용해 브라우저에 Paint
하는 것을 의미한다.
이전 강의에서도 보았지만 Render Tree
를 빠르게 구성하기 위해서는
복잡하지 않은 DOM
, CSSOM
을 만드는 것이 필요하다.
특히 CSSOM
을 복잡하지 않게 만드는 것이 중요하다.
복잡한 CSSOM
이란 선택자를 복잡하게 표기한 것을 의미한다.
예를 들어
<body>
<article>
<div>
<p>
<span> 안녕하세요 ~! </span>
</p>
</div>
</article>
</body>
다음과 같은 노드가 존재 할 때 선택자를
article > div > p > span {color: blue;}
처럼 구성하는 경우가 그렇다.
이런식으로 정의한 CSSOM
은
다음과 같이 depth
가 늘어나게 되는데
이는 Render Tree
를 구성 할 때 DOM
의 span
태그를 만날 때 마다 해당 span
태그가
article > div > p
의 관계를 갖고 있는지 위 노드까지 재확인 해야 하기 때문에 Render Tree
를 구성하는데 시간이 걸린다.
그래서 최대한 Render Tree
를 빠르게 구성하기 위해서는 CSSOM
의 depth
를 낮춰줄 필요가 있는데 이런 경우엔 식별자를 이용하는 것이 좋다.
<body>
<article>
<div>
<p>
<span class="greet"> 안녕하세요 ~! </span>
</p>
</div>
</article>
</body>
다음처럼 스타일을 지정하고 싶은 태그에 식별자를 지정해주면
Render Tree
를 구성하기 위해 DOM
의 노드들을 순회 할 때 식별자만 확인하여
Render Tree
를 빠르게 구성 할 수 있기 때문이다.
식별자를 이용한다는 것은 효과적으로 Render Tree
를 빠르게 렌더링 하는 방법 중 하나이다.
나머지 방법들은 밑에서 실습을 통해 알아가도록 하자
Javascript
로 DOM
을 조작한다는 것script
파일로 DOM
을 조작한다는 것은
앞서 제작한 Render Tree
에 새로운 노드를 추가하거나 기존 노드를 제거하거나
layout , paint
관련 속성을 수정하거나 식별자 (class , id
) 들을 변경하는 행위를 말한다.
Render Tree
를 조작한다는 것은 수정된 Render Tree
를 만들어 User interface
에 띄우겠다는 것이다.
layout , paint
의 일련의 과정을 거쳐서 말이다.
Render Tree
를 수정하면 어떤 일이 일어날까 ? - Dirty bit system
Dirty bit
의 사전적 의미란 메모리의 어떤 정보가 수정되었을 때 모든 메모리의 정보를 순회하며 수정하는 것이 아니라
해당 메모리와 관련있는 메모리의 비트들만 수정 하는 것을 의미한다.
Render Tree
를 수정하는 것도 Dirty bit system
을 이용한다.
어떤 노드가 변경 되었을 때 모든 Render Tree
들을 layout , paint
과정을 거쳐 재렌더링 하는 것은 비효율적이다.
그렇기에 Render Tree
가 수정이 된다면 수정된 노드에게 직접적인 영향을 받는 요소들까지 변경하여 재렌더링 한다.
Reflow
와 Repaint
Reflow
는 layout
관련 속성이 변경되어 브라우저에 노드들의 위치를 새로 계산하는 과정을 의미하고
Repaint
는 브라우저에서 노드들의 시각적인 요소를 변경하여 렌더링 하는 과정을 의미한다.
우리가 Javascript
로 DOM
조작을 하는 행위들은 대부분 Reflow
나 Repaint
를 일으키는 행위들이다.
브라우저에서 노드들이 지속적으로 다른 모습으로 렌더링 됨으로서 인터렉티브한 경험을 주고자 하기 때문이다.
"가끔씩 reflow
는 나쁘고 repaint
는 나쁘지 않다" 라는 말을 심심찮게 들어볼 수 있었을 것이다.
그런 말이 나오는 이유는 reflow
의 경우 기하학적 요소가 변경되면 영항을 받는 노드들이 늘어날뿐더러 필연적으로 reflow
가 일어나면 repaint
도 같이 일어나기 때문이다.
렌더링 과정의 일련적 과정을 기억하자
reflow
가 영향을 미친다는 것들의 의미를 예시를 통해 확인해보자
Reflow
는 Layout
을 재구성 하는 것노드들의 기하학적 속성을 계산하는 Layout
과정에서 살펴보는 스타일 속성들은 다음들과 같다.
모든 스타일 속성들을 이야기 할 수는 없으나 예시 몇 가지를 들으면 어떤 것인지 이해가 될 것이다.
display , top , right , bottom , left , margin , padding , font-size , font-family , width , height ..
들과 같이 노드가 브라우저에 위치할 곳에 영향을 미치는 요소들을 의미한다.
크기 관련 속성들도 layout
에 영향을 미치는 이유는 특정 태그의 크기에 따라
위치가 변경되는 태그들이 필연적으로 존재하기 때문이다.
다음과 같은 예시를 살펴보자
<body>
<div class="parent">
<div class="child"></div>
<div class="sibling"></div>
</div>
</body>
다음과 같이 child
태그를 클릭하면 width , height
만 변경되도록 이벤트 핸들러를 등록해보았다.
child
태그의 width , height
만 변경시켰을 뿐인데 부모 태그의 너비도 함께 늘어나고
형제태그인 sibling
태그의 위치가 변경되는 모습을 볼 수 있다.
이처럼 layout
이 변경되면 자신에게 영향을 받는 노드들의 layout
값도 모두 변경해야 하기 때문에 비용이 높은 액션에 해당한다.
performance
로 살펴본 Reflow
지속적으로 child
노드의 크기가 변경되는 여러 프레임 중 한 프레임을 가져와봤다.
Recalculate Style
은 Render Tree
에서 해당 노드의 width , height
값을 변경하는 것을 의미한다.
layout
값과 관련된 속성을 변경했으니 각 노드들의 layout
값을 다시 계산하는 layout
과정을 거치고
이후 paint
, commit
과정을 통해 브라우저에 렌더링 된다.
이처럼 layout
관련 속성이 변경되면 영향을 받는 노드들의 layout
관련 속성을 계산하는 과정이 필연적으로 나타난다.
layout
과정은 필연적으로 발생할까 ?이번에는 아예 부모 태그의 width , height
를 절대적으로 지정해주고 child
노드의 크기를 변경시켜보았다.
child
노드의 크기 변경에 상관 없이 부모 태그의 크기를 절대적으로 고정시켜뒀단 것이다.
그러면 어떻게 될까 ? 부모태그는 영향을 안받을 것이니 layout
단계가 발생 안하지 않을까 ?
발생한다.
또한
layout
까지 걸리는 시간이 줄어들지 않을까 했는데 동일하였다.
이러한 이유가 발생하는 이유는 변화 유무에 상관없이 layout
변경으로 인해 발생하는 reflow
과정에서
상위 태그 레이아웃에 잠재적인 영향을 미칠 수 있기 때문에 직접적으로 영향을 받을 것이라 예상되는 노드들의 layout
을 모두 순회하며 계산하기 때문이다.
그러니 변화 유무에 상관없이 reflow
는 관련 노드의 모든 layout
을 둘러본다는 것이다.
Reflow
시 layout
계산 시간을 단축시키는 방법해당 방법들은 모두
transtion : ..
스타일 속성을 사용할 때를 가정한다.
inline style
을 1px씩 늘리는 것보다
inline style
한 번에n px
로 늘리고transition
속성을 사용하는 것이
훨씬 효율적이다.
그럼 reflow
시 layout
계산에 걸리는 시간을 단축 시키려면 어떻게 해야 할까 ?
그것은 영향을 받는 노드들을 최소화 시키는 것이다.
특정 노드의 layout
속성이 변경되면 다른 노드들의 layout
이 영향을 받는 이유는 DOM
이 static relationship
을 갖기 때문이다.
우리가 DOM
을 구성하면 DOM
은 계층적 구조를 가지며 각 태그들은 계층적 구조의 영향을 받아 문서 흐름에 맞게 포지션이 설정되는 것을 볼 수 있다.
부모 태그 내부에서
div
태그들은 차곡차곡 쌓이는 것 처럼 말이다.
기본적으로 태그들의position
속성이static
으로 설정되는 것을 생각해보자
이러한 계층적 구조때문에 layout
을 변경하면 계층적 구조의 노드들을 탐색하며 모든 layout
을 재구성 하는 것이다.
그럼 우리가 해야 할 일은 layout
의 변경이 다른 노드들에 영향을 미치지 않게 문서 흐름에서 벗어나도록 하는 것이다.
문서의 흐름에서 벗어나게 할 수 있도록 position
을 흐름에 맞게 배치시키는 것이 아니라
position : absolute
나 position : fixed
로 고정시켜보자
이벤트 핸들러를 변경해주었다.
const $child = document.querySelector('.child'); document.addEventListener('DOMContentLoaded', () => { $child.style.width = '300px'; $child.style.height = '300px'; });
최대한 다른 영향이 없도록 브라우저에 렌더링 되는 즉시 크기와 높이를 변경시켜주었다.
position : static
과 position : absolute
의 layout
시간 비교시간을 비교하기 앞서 모두 transtion : all 5s
로 설정해주고 동일한 이벤트 핸들러를 등록해주었다.
변경되는 것은 position
속성값 뿐이다.
position : static
static
일 경우 전체 layout
계산에 걸린 시간은 이다.
position : absolute
position
을 absolute
로 변경해주고 layout
계산에 걸린 시간은 이다.
이러한 결과가 나타나는 이유는 앞서 말했듯 position : absolute
등으로 설정하게 되면 layout
을 다른 노드들에게 영향을 미치지 않았기 때문이다.
reflow
를 발생시키고자 한다면 최대한 다른 노드들의 layout
에 영향을 미치지 않도록
설정해주어야 한다.
Repaint
Repaint
는 비쥬얼 관련 속성이 변경되었을 때 브라우저에 다른 모습으로 변경되는 것을 말한다.
이 또한 변경이 된 노드들에 대해서만 변경이 일어나고
시각적 속성은 다른 노드들의 layout
에 영향을 미치지 않기 때문에 리소스 집약적이다.
그러니 Reflow
보다는 비용 측면에서 효율적이라는 것이다.
Repaint
만 일으키도록 DOM
을 조작해보자
const $child = document.querySelector('.child');
document.addEventListener('DOMContentLoaded', () => {
const colorsArray = ['pink', 'red', 'green'];
const maxInterval = 30;
let curInterval = 0;
const interval = setInterval(() => {
$child.style.backgroundColor = colorsArray[curInterval % 3];
if (curInterval++ > maxInterval) clearInterval(interval);
}, 500);
});
setInterval
로 구현되어 타이머가 발화되면 Render Tree
에서 스타일 속성을 변경하는
Recalculate Style
이 일어나고
그 이후에는 Paint => Commit
만 일어난 모습을 볼 수 있다.
Layout
을 계산하는 과정은 없다.
그럼 어떻게 해야 Repaint
를 보다 효과적으로 할 수 있을까 ?
Repaint
자체를 최소화 하자const $child = document.querySelector('.child');
$child.style.backgroundColor = 'red';
$child.style.border = '2px solid white';
$child.style.boxShadow = '0px 0px 10px 10px white';
$child.style.borderRadius = '20px';
다음처럼 개별적인 스타일 요소들을 한 코드에 한 줄씩 변경하는 것보다
$child.style.cssText = 'background-color: red; border: ... box-shadow: 0px 0px 10px 10px white;';
다음처럼 cssText
속성(인라인 스타일
)을 한 번에 변경함으로서 Repaint
횟수를 줄일 수 있다.
$child.className += 'shadowBox'
더 좋은 방법은 식별자를 추가하거나 삭제하는 것으로 더 관리를 용이하게 할 수 있다.
킥.. 그런데 개발자도구에서 확인하니 큰 차이가 없더라
실제로 방법에 차이가 있는지를 확인해보려고 개발자 도구를 켜봤더니 차이는 없었다
이는 브라우저 엔진이Repaint
계산을 최소화 하기 위해 다양하게 최적화 하기 때문이라고 한다.스타일 변경사항을 일관 처리 하거나 스타일 변경의 패턴을 그룹화 하여 최적화 하거나
Reflow , Repaint
를 일으키는 코드들을 수집한 후 한 번에 평가한다. (지연 평가)
더 많은 내용들은 브라우저 엔진이 어떻게 돌아가는지를 더 알고 나서 포스팅하려고 한다 :)
더 다양한 내용들을 담고 있는 아티클을 함께 첨부하도록 하겠다 !
Understanding Reflow and Repaint in the browser
CSS will-change 프로퍼티에 관해 알아둬야 할 것