근래에 웹 최적화에 관해 관심을 가지면서 Reflow
와 Repaint
에 대해서 다시 한번 짚고 넘어가 보려고 합니다.
혹시나 브라우저 렌더링에 대해 상기하고 싶으신 분이 있다면 이전 글을 보고 오시는 것을 추천합니다.
이 글은 아래와 같은 목차로 진행하겠습니다.
Reflow
란 HTML 파일을 불러와 Parsing을 통해 Render Tree
를 형성하게 되는데, 어떠한 액션이나 이벤트로 인해 Dom의 크기나 위치 등의 값이 변경되어 Layout 과정을 다시 수행하게 됩니다.
이렇게 다시 수행되는 과정을 통틀어서 Reflow라고 합니다. 말로 하면 감이 안 잡힐 수도 있습니다. 예제 코드를 보고 오겠습니다.
function reflowTrigger(){
document.getElementById("btn").style.width="100px"
document.getElementById("btn").style.height="200px"
}
//html
<div onclick='reflowTrigger()' id="btn"
style="width:150px; height:150px; background-color:green">
클릭하세요
</div>
위와 같이 btn
이라는 이름을 가진 버튼을 클릭하면 reflowTrigger
가 호출되면서 버튼의 크기가 달라지는거 때문에 어디에 배치할지를 브라우저는 고민합니다. 이런 과정을 Reflow
라고 합니다. Reflow
가 자주 발생하면 안 좋은 이유는 변경 시 영향을 받는 모든 노드(자신, 자식, 부모 노드) 또한 계산을 해야 하기 때문에 비용이 높습니다. 추가로**Reflow
가 발생하면 Repaint
발생은 불가피합니다.**
Reflow가 발생하는 경우
DOM 노드의 추가, 제거
DOM 노드의 위치 변경
DOM 노드의 크기 변경(margin, padding, border, width, height 등..)
CSS3 애니메이션과 트랜지션
폰트 변경, 텍스트 내용 변경
이미지 크기 변경
offset, scrollTop, scrollLeft과 같은 계산된 스타일 정보 요청
페이지 초기 렌더링
윈도우 리사이징
Repaint
는 말 그대로 다시 그리는 것을 뜻하며 Render Tree
변경으로 발생합니다. 즉, 시각적인 부분이 변경되면 Repaint
가 발생한다고 보시면 됩니다.
Reflow
와 Repaint
는 둘 다 Render Tree
가 변경되어 발생한다는 점은 같습니다. 그러나 차이점이 있습니다. Reflow
는 쉽게 요소의 크기 또는 배치가 변경됐을 때 발생하고, Repaint
는 요소의 사소한 게 변경됐더라도 Render Tree
가 변경됐으면 Repaint
는 무조건 발생합니다. 그러면 아래와 같은 상황에는 Reflow
와 Repaint
둘다 발생할까요?
function reflowTrigger(){
document.getElementById("btn").style.color="red"
}
//html
<div onclick='reflowTrigger()' id="btn"
style="width:150px; height:150px; background-color:green">
클릭하세요
</div>
생각해보셨을까요?? 정답은 “아니요” 입니다. 위의 코드를 실행하게 되면 Repaint만 발생합니다. 왜냐하면 btn div는 크기나 레이아웃의 변경이 된 것이 아닌 색만 변경된 것이기 때문에 Reflow
는 발생하지 않고 Repaint
만 발생합니다.
정리해보자면 Reflow
가 발생하면 Repaint
는 필연적으로 발생한다고 볼 수 있지만, 그 반대로 Repaint
가 발생했다고 Reflow
가 무조건 발생했다고 보기는 어렵습니다.
즉, 화면의 구조가 변경될 때는 Reflow
와 Repaint
가 모두 발생하지만 그 외의 변경은 Repaint
만 발생한다고 보시면 됩니다.
Reflow
와 Repaint
중 다른 요소에도 영향이 가고 그로 인해 Latency를 유발하는 Reflow
가 자주 발생하지 않도록 코딩해야합니다.
그럼 어떻게 코딩해야 할까? 아래를 참고하시면 좋습니다.
클래스 변화에 따른 스타일 변화를 원할 경우, 최대한 DOM 구조 상 끝단에 위치한 노드에 추가합니다.
→ 리플로우의 영향범위를 전체 노드가 아닌 일부 노드들로 제한할 수 있습니다. 그렇다고 reflow가 발생하지 않는 것은 아닙니다.
인라인 스타일을 배제합니다.
애니메이션이 들어간 element는 가급적 position: fixed
또는 position: absolute
사용합니다.
→ 일반적으로 JS (특히 jQuery)나 CSS3로 width/height 또는 위치이동을 구현한 애니메이션은 거의 초 단위로 상당한 Reflow를 발생시킵니다. 이러한 상황에 해당 개체의 position 속성을 fixed 또는 absoute로 주게 되면 다른 요소들의 레이아웃에 영향을 끼치지 않으므로 페이지 전체의 Reflow 대신 해당 애니메이션요소의 Repaint만을 발생시킵니다. 이것은 비용적인 측면에서 매우 효율적인 방법입니다.
JS를 통해 스타일 변화를 주어야 할 경우, 가급적 한번에 처리합니다.
// Bad : 여러 번에 걸쳐 reflow , repaint 발생된다.
const toChange = document.querySelector("div");
toChange.style.color= "#fff";
toChange.style.border = "1px solid #ccc";
// Good : css에서 미리 속성 지정 후 한번의 변화를 발생.
/* in CSS */
.elem {
border:1px solid #000;
color:#000;
}
.highlight {
border-color:#00f;
color:#fff;
background:#333;
}
/* in JS */
document.querySelector(".elem").className = "highlight";
그럼 어떻게 Reflow가 발생했고 Repaint가 발생했는지 볼 수 있을까요??
아래와 같이 확인하실 수 있습니다.
Reflow
를 지양하자고 했지만, 현실적으로 Reflow
를 생각하면서 코딩하기 쉽지 않습니다. 그러나 이 글을 쓴 이유는 웹의 최적화를 고민할 때 이런 방법도 있겠다고 알리기 위해 포스팅했습니다. 저 또한 이번 기회를 통해서 Reflow
와 Repaint
에 대해 깊게 공부하게 됐습니다.