Hello,

kok nae-ga ha-myun an-dweneun MAGIC...🧚

웹 프로그래밍/공부일지

웹 페이지의 랜더링 과정 2편

✿도담도담 2018. 9. 25. 12:06

1편 - [Web Programming/Frontand Challenge] - 웹 페이지의 랜더링 과정 1편

참고 블로그 : http://12bme.tistory.com/140



1편에서 웹 페이지의 랜더링 순서를 1단계부터 5단계 까지 알아보았다.

이번 2편에서는 리플로와 리페인트에 대해 알아보겠다.

.

.

레더링이 모두 완료된 상태에서 ( 페인트까지 끝낸 상태 ) 스타일이 바뀌거나

새로 나타나는 노드가 있다면...? 또는 데이터 변경으로인해 ( ex. AJAX ) 다시 표시해 주어야하는 일이 생긴다면?

아마 다시 스타일을 적용하고 ( 랜더 트리 작업 ) 페인트 해주어야 할 것이다.

이럴 경우에 발생하는 것이 리플로와 리페인트이다.



리플로 ( Reflow )란?

변경이 생긴 랜더 트리에 대해 유효성 확인을 하고 노드의 크기와 위치를 다시 계산하는 작업이다.

랜더 트리 작업과 레이아웃 처리를 다시 하는 것 같다.

리페인트 ( Repaint )란?

변경된 노드를 화면에 업데이트 하여 표시해주는 것을 의미 한다.

리플로가 발생 했을 때, 색 변경등 단순 스타일 변경이 일어날 시 발생합니다.


다시 계산되고 뿌려주는 작업이니 당연히 처리 비용이 발생 되고 ( 리플로우가 리페인트 보다 비용이 높다. )

많이 발생할 시 웹 페이지를 표시하는데에 시간이 걸려 사용자가 불편함을 느낄 수 있다. 😒

따라서, 이를 어떤 방법으로 줄이는지 알아 보자.




소개된 방법으로는 총 5가지가 있다.


① 작업 그루핑

항상 이름을 보면 대충 어떤건지 알 수 있는 것 같다.

작업 그루핑은 비슷한 형태의 작업을 묶어 실행 되도록 해주는 것이다.

참고한 블로그의 코드 예제를 보면 이해 하기 쉽다. 

1
2
3
4
5
6
7
function change() {
    var width = document.getElementById("layer1").style.width;
    document.getElementById("layer2").style.width = width;
    var height = document.getElementById("layer3").style.height;
    document.getElementById("layer4").style.height = height;
}
출처: http://12bme.tistory.com/140 [명확함은 충일함에서 나온다.]
cs

위의 코드를 보면 스타일 정보 요청 후 스타일을 변경하고 있다. 

이렇게 코드를 사용할 경우 동일한 형태의 작업이 두번 반복되면서 리플로가 여러번 발생할 수 있다.

따라서, 아래 처럼 비슷한 역할을 하는 코드를 묶어주도록 순서를 바꾼다.

1
2
3
4
5
6
7
function change() {
    var width = document.getElementById("layer1").style.width;
    var height = document.getElementById("layer3").style.height;
    document.getElementById("layer2").style.width = width;
    document.getElementById("layer4").style.height = height;
}
출처: http://12bme.tistory.com/140 [명확함은 충일함에서 나온다.]
cs

② 실행 사이클

참고 블로그를 읽으면서 가장 뭔소린지 몰랐다 ㅎ... "이벤트 루프 모델"을 몰라서 ^^..( 부끄 😣 )

http://eine.tistory.com/40 사이트에서 또 참고해서 공부했다 ㅎ... 머리가 나빠서 재깍재깍 이해를 못하는지라 세번정도 읽었다.

랜더링 참고 사이트에서는 ' 이러한 실행 사이클로 인해 타이머를 사용하면 수차례의 리플로와 리페인트가 발생 될 수 있다. ' 라고 적혀있다. 이벤트 루프 모델 설명을 보면 바로 이해가 되니 넘어가겠다!

( 타이머 함수를 사용함으로써 중복 발생이 일어나기에 타이머를 사용하지 말라라고 적어 논것 같다. )


③ 노출 제어를 통한 리플로 최소화 방법

③ - ① display 속성

앞에서 잠깐 언급을 했던 것 같은데 ( 🤔..? ) 

display 속성이 none이라면 화면에 보여지지 않는 친구가 되므로 랜더 트리를 만들 때 포함이 되지 않는다.

이를 이용한 방법이다.

1
2
3
4
5
6
7
8
9
10
var element = document.getElementById("box1");
 
for(var i=50; i<100; i++) {
    element.style.width = i + "px";
}
 
for(i=1; i<=50; i++) {
    element.style.borderWidth = i + "px";
}
출처: http://12bme.tistory.com/140 [명확함은 충일함에서 나온다.]
cs

위의 코드와 같이 box1의 크기를 변경하게 된다면 값이 변경이 될 때마다! 리플로와 리페인트가 발생한다. ( 아주 나쁨..! )

만약 위의 코드를 아래와 같이 변경 한다면..?


1
2
3
4
5
6
7
8
9
10
11
12
13
var element = document.getElementById("box1");
element.style.display = "none";
 
for(var i=50; i<100; i++) {
    element.style.width = i + "px";
}
 
for(i=1; i<=50; i++) {
    element.style.borderWidth = i + "px";
}
 
element.style.display = "block";
출처: http://12bme.tistory.com/140 [명확함은 충일함에서 나온다.]
cs
처음 노드를 display none으로 속성을 준 뒤 크기 변경을 모두 처리한 후 다시 나타나게 처리 되었다.

이럴 경우 리플로, 리페인트가 display block시점 ( 보여지는 시점에 ) 한번만 일어나게 된다. 👍


③ - ② 노드 복제

변경하고싶은 요소의 노드를 복제 후 복제된 노드를 변경하는 방법니다.

맨처음 노드 복제 후 > 스타일 변경 > 대체 

1
2
3
4
5
6
7
8
9
10
var element = document.getElementById("box1");
var clone = element.cloneNode(true);     // 원본 노드를 복제한다.
 
for(var i=0; i < 100; i++) {
    clone.style.width = i + "px";
}
 
// 변경된 복제 노드를 DOM 트리에 반영하기 위해 기존 노드와 치환한다.
parentNode.replaceChild(clone, element);
출처: http://12bme.tistory.com/140 [명확함은 충일함에서 나온다.]
cs


③ - ③ createDocumentFragment() 메서드 사용

함수 설명 : https://www.w3schools.com/jsref/met_document_createdocumentfragment.asp

- 위와 동일한 구조


④ 캐싱

컴정과인 나에겐 이제 이름만 봐도 알 것 같은 방법이다..😤

캐싱은 별도의 변수에 자주 사용하는 값을 저장하는 방법이다.

특정 속성과 메소드를 사용하기만 해도 리플로가 발생 된다. 

( 따로 첨부하지 않았지만 참고 블로그에 보시면 리플로 발생 메소드가 정리 되어 있습니다. 그중에 하나가 scrollWidth )

아래의 코드에서 scrollWidth 를 호출할 때마다 리플로가 계속 발생하고 있다.

1
2
3
4
5
for(condition) {
    el.style.width = el.scrollWidth + "px";
    el.style.left - el.scrollLeft + "px";
}
출처: http://12bme.tistory.com/140 [명확함은 충일함에서 나온다.]
cs

아래처럼 변수에 담아 리플로가 한번만 발생하도록 사용하자.

1
2
3
4
5
6
7
var nWidth = el.scrollWidth, nLeft = el.scrollLeft;
 
for(condition) {
    el.style.width = nWidth + "px";
    el.style.left = nLeft + "px";
}
출처: http://12bme.tistory.com/140 [명확함은 충일함에서 나온다.]
cs


⑤ CSS 규칙

자바스크립트에서 자주 스타일의 속성값을 변경할텐데 이 때 요소의 개수가 많아질수록 랜더링 성능이 저하 될 수 있다.

이런 경우에는 각 요소에 하나하나 접근하기 보다는 css 규칙을 만들어 바꿔주는 것이 좋다.

*주의할 점*은 대상 요소의 수가 많지 않을 경우 오히려 개별 요소에 접근해 처리하는 방식이 더 빠르다. 

( 항상 규칙이 빠르지 않다. )

1
2
3
4
5
6
7
8
9
document.getElementsByTagName("head")[0].appendChild(document.createElement("style"));
var oStyle = document.styleSheets[document.styleSheets.length - 1];
 
if(oStyle.addRule) {     // 인터넷 익스플로러의 경우
    oStyle.addRule("div""background-color:green");
else {         // 그 밖의 바라우저
    oStyle.insertRule("div {background-color:green;}"0);
}
출처: http://12bme.tistory.com/140 [명확함은 충일함에서 나온다.]
cs