OffsetTop을 읽으면 reflow가 발생한다?

Mar 25, 2021

What forces layout / reflow 라는 문서를 보는데 Getting box metricsoffsetTop, getBoundingClientRect() 등을 호출하는 api도 적혀 있었다.
아니 이게 무슨 소리야…내가 reflow를 마구 발생시키고 있었다니…?

그런데 댓글을 보다보니 퍼포먼스탭에서 reflow를 확인할 수 없었다는 얘기도 있어서 실제로 offsetTop을 읽는 테스트 화면을 구현해서 퍼포먼스탭을 보니 reflow가 발생하지 않았다. 음?

이 부분이 궁금해서 여러가지 글들을 읽어봤는데, offsetTop을 읽는 것 자체가 무조건 reflow를 발생시키지는 않는다. 하지만 offsetTop을 읽기 위해서 브라우저가 렌더링 큐에 쌓인 모든 작업을 수행하면서 reflow를 발생시킬 수 있다고 한다.
브라우저는 최적화를 위해 reflow가 필요한 여러개의 작업을 묶어서 큐에 쌓아서 대기하다가 한번의 reflow로 전부 처리할 수 있도록 하는데, offsetTop 등의 계산된 스타일에 get 요청을 하면 최신값을 읽기 위해 큐를 flush하고 모든 변경사항을 적용한다.

구글 개발자들이 렌더링 퍼포먼스를 개선하는 방법으로 작성한 글 중 강제 동기식 레이아웃 피하기 & 레이아웃 스래싱 피하기도 같은 원리다.

// bad
function resizeAllParagraphsToMatchBlockWidth() {
  for (let i = 0; i < paragraphs.length; i++) {
    paragraphs[i].style.width = box.offsetWidth + 'px';
  }
}

// good
const width = box.offsetWidth;
function resizeAllParagraphsToMatchBlockWidth() {
  for (let i = 0; i < paragraphs.length; i++) {
    paragraphs[i].style.width = width + 'px';
  }
}

위와 아래 코드의 결과는 동일하지만 성능 차이가 나는 이유이기도 하다. 단순히 box.offsetWidth를 읽는게 문제가 아니고, paragraphs[i]의 너비가 변경되면서 스타일이 변경되었기 때문에 box.offsetWidth의 정확한 계산값을 얻기 위해 큐를 flush하고 스타일을 적용해야 하므로 화면을 반복해서 reflow - repaint 하게 된다.

참고