브라우저의 렌더링과 리플로우 리페인팅

5_wintaek·2024년 2월 29일
0

📚 들어가며

오늘 면접 스터디에서는 리플로우와 리페인팅에 대해 설명 및 예상 면접 질문들을 만들어 팀원들에게 소개하였다. 우선, 리플로우와 리페인팅 설명 전에는 브라우저 렌더링의 대해 간단하게 설명하는게 좋다고 생각하여 렌더링 설명을 추가하였다. 또한 블로그에 한번 더 정리하면서 짚고 넘어가는게 좋다는 생각을 하여 이 글을 작성하게 되었다.

브라우저의 렌더링이란 ?

렌더링이란 HTML,CSS,JS 등 개발자가 작성한 문서가 브라우저에서 출력되는 과정을 말한다.

렌더링 동작과정은 어떻게 될까?

  1. HTML 파일과 CSS 파일을 파싱해서 각각 Tree를 만든다. (Parsing)
  2. 두 Tree를 결합하여 Rendering Tree를 만든다. (Style)
  3. Rendering Tree에서 각 노드의 위치와 크기를 계산한다. (Layout)
  4. 계산된 값을 이용해 각 노드를 화면상의 실제 픽셀로 변환하고, 레이어를 만든다. (Paint)

parsing

브라우저가 페이지를 렌더링 하려면 가장 먼저 받아온 HTML파일을 해석해야 한다. Parsing 단계는 HTML 파일을 해석하여 DOM TREE를 구성하는 단계이다.

파싱 단계에서 HTML에 CSS가 포함되어 있다면 CSSOM Tree 구성 작업도 함께 진행이 된다.

Style

style은 parsing 단계에서 생성된 DOM Tree와 CSSOM Tree를 매칭시켜 하나의 Render Tree로 구성한다. 여기서 Render Tree란 실제로 화면에 그려질 Tree이다.

예시로 들면 Render Tree를 구성할 때 visibility: hidden은 요소가 공간을 차지하고, 보이지만 않기 때문에 Render Tree에 포함이 되지만, display: none 의 경우 Render Tree에서 제외된다.

Layout

Layout 단계에서는 Render Tree를 화면에 어떻게 배치해야 할 것인지 노드의 정확한 위치와 크기를 계산하는 단게를 뜻한다.

루트부터 위로 올라가면서 노드를 순회한다. 노도의 정확한 크기와 위치를 계산하고 Render Tree에 반영한다. 만약 크기 값을 %로 지정하였다면, Layout 단계에서 %값을 계산하여 픽셀 단위로 변환하는 역할을 한다.

paint

Paint 단게에서는 Layout 단게에서 계산된 값을 이용하여 Render Tree의 각 노드를 화면상의 실제 픽셀로 변환한다. 이 때 픽셀로 변환된 결과는 하나의 레이어가 아니라 여러 개의 레이어로 관리된다.

스타일이 복잡할수록 Paint 시간도 늘어난다. 예시로 들면, 단색 배경의 경우 시간과 작업 비용이 적게 들어가지만, 그림자 효과는 시간과 작업 비용이 더 많이 필요하다.

🤔 그렇다면 브라우저는 어떻게 동작하는가

  1. 사용자가 www.example.com(도메인 주소)에 접속한다.
  2. 이 때 브라우저는 도메인 주소로 네트워크 요청을 보낸다.
  3. 만약 이 사이트를 한번도 방문한 적이 없다면 브라우저는 DNS 조회 요청한다.
  4. 찾은 IP 주소로 요청으 전송하여 도메인 주소에 해당하는 리소스를 응답으로 받는다.

DNS(Domain Name System)

인터넷에서 사용되는 도메인 이름과 IP 주소를 연결하는 시스템이다. 인터넷 상에서 컴퓨터나 네트워크 장치는 IP 주소를 사용하여 통신한다. 하지만 이 IP 주소는 사람이 기억하기 어렵고 숫자로 이루어져 있어 사용자에게 어려움을 제공한다.

DNS는 이러한 불편함을 해소하기 위해 도메인 이름(예: www.example.com)과 IP 주소를 매핑해 주는 시스템으로 작동한다. 사용자가 웹 브라우저에 도메인 이름을 입력하면, DNS는 해당 도메인 이름에 대응하는 IP 주소를 찾아내고 이를 통해 요청된 서버에 접속한다. 이렇게 하면 사용자는 기억하기 쉬운 도메인 이름을 사용하여 웹사이트에 접속할 수 있다.

🤔 브라우저 렌더링과 reflow,repaint의 관계

  • Layout은 앞서 설명했듯이 요소의 위치,크기를 계산하여 화면에 그리는 과정인데 reflow 와 관련있다.
  • paint는 배치된 요소에 스타일을 적용하는 과정으로 repaint와 관련있다.

✔️ 리플로우(reflow)와 리페인팅(repaint)

reflow와 repaint는 요소가 시각적으로 변경되었을 때, 변화를 계산하여 화면에 그려주는 작업을 말한다. DOM이 시각적으로 변경되면 reflow가 발생하여 렌더트리를 재생성하고 생성된 렌더트리를 기반으로 요소를 화면에 그리는 repaint가 발생한다.

✔️ 리플로우(reflow)

  • 리플로우는 브라우저가 페이지를 렌더링할 때 발생하는 과정 중 하나로, 요소들의 크기나 위치 등이 변경될 떄 다시 계산되고 레이아웃이 갱신되는 과정을 말한다.
  • 리플로우는 비용이 큰 작업인데, 그 이유는 특정 요소에서 리플로우가 발생하면 주변 요소(부모,자식,형제)에도 영향을 주기 때문이다.

🤔 특정 요소의 너비 변경이 주변 요소에 리플로우를 일으킨다? 무슨말일까?

  • 만약 세 개의 요소가 있다고 가정하고, 이 중 형제 2의 너비를 변경했다.
  • 형제의 경우 너비를 변경하므로 리플로우가 발생한다.
  • 그러나 형제2의 너비 변경으로 인해, 인접한 형제3의 위치도 변경되어 리플로우가 발생한다.
  • 요소 하나의 변화가 주변 요소의 위치나 크기에 영향을 주어, DOM트리 계산 작업이 발생하고 렌더 트리가 재생성 되는 것이다.

🤔 reflow 발생 시점은 언제일까?

a. DOM 요소의 크기,위치,구조 등 속성이 변경될 떄(width,height 등)
b. 브라우저 사이즈가 변할 때
c. 텍스트 내용, 이미지 등 컨텐츠가 동적으로 변경될 때 d. JS DOM관련 메소드를 실행하거나 속성에 접근할 때

✔️ 리페인트(repaint)

  • 변경된 요소를 화면에 그려주는 작업을 리페인트 라고 한다.
  • 리페인트는 요소의 스타일이 변경되었을 때 발생하므로 리플로우 보다 비용이 적다.

🤔 리페인트 발생 시점은 언제일까 ?

a. 리플로우가 발생했을 때
b. 요소의 스타일(색상,배경색 등)이 변경되었을 때
c. visibility 속성, opacity 속성이 변경될 때
d. 요소의 포커스가 변경될 때

🤔 visibility가 변경되는게 리페인트면 display:none 도 리페인트인가요 ?

  • 앞에서 렌더링 과정에서 설명했듯이 visibility : hidden은 단순히 보이지 않을뿐 크기나 위치가 변하는게 아니기에 리페인트만 발생한다.

  • display:none 같은 경우에는 영역을 차지하지 않으면서 보이지 않는다. 즉, Render Tree에서 제외 된다는 말이므로, display:none이 적용된 요소는 사라지면서 주변 요소의 위치, 크기에도 영향을 주기 때문에 리플로우,리페인트가 발생한다.

🤔 리플로우 줄이는 방법이 뭐가 있을까요 ?

1) display : none 이용하기

display none 이 적용되면 그 요소는 렌더 트리에서 제외되고 없는 요소 취급을 당하기 때문에 이 요소에 다른 기하학적 변화가 일어나도 리플로우나 리페인트는 발생하지 않는다.

div.style.display = "none";   // (A) 렌더트리에서 제외시키기

// 스타일 수정

div.style.display = "block";  // (B) 스타일 수정 후 렌더트리에 추가하기

(A),(B)에서 각각 리플로우 리페인트가 1번씩 발생하기에 많은 스타일이 변경되는 경우 비용을 절감할 수 있다.

2) 이벤트 핸들러 사용시 DOM을 조작하는 경우도 리플로우가 발생하니 이벤트를 효율적으로 처리

JS로 여러 DOM의 속성을 변경할 때 코드 순서에 따라 리플로우 횟수를 줄일 수 있다.

// BAD
const el1 = document.querySelector('.target-first');
el1.style.width = '10px';

const el2 = document.querySelector('.target-second');
el2.style.width = '10px';

const el3 = document.querySelector('.target-third');
el3.style.width = '10px';

아래와 같이 DOM의 스타일을 변경하는 코드를 모아두면 리폴로우 횟수를 1번으로 줄일 수 있다.

// GOOD

const el1 = document.querySelector('.target-first');
const el2 = document.querySelector('.target-second');
const el3 = document.querySelector('.target-third');

// dom의 스타일 변경 코드를 한 곳으로 모아둠
el1.style.width = '10px';
el2.style.width = '10px';
el3.style.width = '10px';

3) 리플로우 유발 함수의 호출을 제한하기
리플로우를 발생시키는 함수나 속성을 매번 호출하지 않고 변수에 저장하는 방법이다.

// 나쁜 예
for(let i=1; i<10; i++){
    div.style.left = (div1.getBoundingClientRect().left + i) + 'px';
}

// 좋은 예
let {left} = div1.getBoundingClientRect();  // reflow 유발 메서드는 변수에 저장해 사용 
for(let i=1; i<10; i++){
    div.style.left = (left + i) + 'px';
    left += i;
}

4) CSS 클래스로 스타일 변경하기
스크립트로 CSS 속성을 여러번 수정하는 것은 리플로우를 여러번 유발시키게 된다.CSS 수정이 많이 필요한 경우에는 CSS 클래스를 정의해두고 사용하자

// BAD
el.style.width = "10px";
el.style.height = "10px";
el.style.borderRadius = "5px";
el.style.backgroundColor = "red";
el.style.left = "20px";

// GOOD
<body>
  <script>
    el.className = 'small';
  </script>
</body>

<style>
.small {
  width: 10px;
  height: 10px;
  border-radius: 5px;
  background-color: red;
  left: 20px;
}
</style>
    </script>

🤔 리페인트 줄이는 방법은 뭐가 있을까요 ?

1) 가상 DOM 사용하기 ex) :React

2) 이미지 및 그래픽 최적화 하기

a. 이미지 포맷 최적화 하기
JPEG vs. PNG vs. GIF: 각 이미지 포맷은 서로 다른 사용 사례가 있다. JPEG는 사진과 같은 복잡한 이미지에 적합하며, PNG는 투명성이 필요한 이미지에, GIF는 간단한 애니메이션에 유용하다.

3) 성능 분석 도구를 사용하여 최적화 할 수 있는 부분 식별하기

a. 우선 [개발자도구] > [성능] 탭을 연다.
b. 기록 버튼을 클릭한다.
c. 기록이 되는 동안, 요소의 글자 색상을 변경한다.
d. 기록을 중단하고 결과를 보면, 요소의 글자 색상을 변경했을 때 페인트만 발생한 걸 알 수 있다.

만약 브라우저에서 클릭이나 스크롤 동작을 했을 때 리플로우 리페인트가 발생하는지 알고 싶다면 이렇게 해보자.

a. [개발자도구] > [렌더링] 탭을 열면 페인트, 레이아웃이 발생할 때마다 하이라이트를 할 수 있다.

😎 마무리

이로써 리플로우와 리페인트를 공부하면서 브라우저 렌더링 과정까지 알게 되는 좋은 시간을 갖게 되었다. 이론적으로는 공부를 많이 해보았으니 실제로 프로젝트에 적용하면서 몸으로 익히는 습관을 늘려야겠다는 생각을 하게된다.

Ref
리플로우, 리페인트와 브라우저 렌더링 알아보기
Naver.D2
테코블 - 브라우저 렌더링 과정 이해하기
브라우저 렌더링 - 리플로우(reflow) 와 리페인트(repaint)

profile
물음표를 느낌표로 바꾸는 개발자

0개의 댓글