브라우저는 화면을 어떻게 그리는가
브라우저가 어떤 과정을 거쳐 화면을 그리는지 알아보자!
Last updated
브라우저가 어떤 과정을 거쳐 화면을 그리는지 알아보자!
Last updated
DOM + CSSOM > 렌더 트리 > 레이아웃 > 페인트 > 컴포지트
브라우저는 기본적으로 위와 같은 과정을 거쳐 화면을 그린다. 이러한 과정을 Critical Rendering Path 또는 Pixel Pipeline 이라고 한다. 이번 글에서는 각 과정에 대해 알아보고자 한다.
가장 처음에는 HTML 과 CSS 등 화면을 그리는 데 필요한 리소스를 다운로드 한다. 다운로드한 HTML 은 브라우저가 이해할 수 있는 형태로 변환하는 parsing 과정을 거친다. 그렇게 해서 요소 간의 관계가 tree 구조로 표현되어 있는 DOM(Document Object Model)을 만든다.
마찬가지로 CSS 도 브라우저가 이해할 수 있는 형태로 변환된다. 변환 결과, CSSOM(CSS Object Model)이라는 트리 구조가 생성된다. CSSOM은 각 요소가 어떤 스타일을 포함하고 있는지를 나타낸다.
렌더 트리는 DOM 과 CSSOM 의 결합으로 생성된다. 이 렌더 트리는 화면에 표시되는 각 요소의 레이아웃을 계산하는 데 사용된다. 달리 말하면, display: none 으로 설정되어 화면에 표시되지 않는 요소는 렌더 트리에 포함되지 않는다.
display: none 은 렌더 트리에 포함되지 않지만, opacity: 0 이나 visibility: hidden 인 요소는 렌더 트리에 포함된다. 이러한 속성은 사용자 눈에는 보이지 않지만 요소 자체가 없어진 것은 아니기 때문이다.
렌더 트리가 완성 되면, 레이아웃 단계로 넘어간다. 이 레이아웃 단계에서는 화면 구성 요소의 위치나 크기를 계산하고, 해당 위치에 요소를 배치하는 작업을 한다. 말 그대로 화면의 레이아웃을 잡는 과정이다.
화면에 요소의 위치와 크기를 잡아 놨으니, 이제 색을 입힐 차례이다. 페인트 단계에서는 화면에 배치된 요소에 색을 채워 넣는 작업을 한다. 예를 들어 배경 색을 채워 넣거나 글자 색을 결정하거나 테두리 색을 변경한다. 이때 브라우저는 효율적인 페인트 과정을 위해 구성 요소를 여러 개의 레이어로 나눠서 작업하기도 한다.
마지막으로 컴포지트 단계는 각 레이어를 합성하는 작업을 한다. 페인트 단계에서 설명한 것처럼 브라우저는 화면을 그릴 때 여러 개의 레이어로 화면을 쪼개서 그린다. 그런 다음 마지막에 그 레이어를 하나로 합성하는데, 그 단계가 바로 컴포지트 단계이다.
브라우저 렌더링 과정을 살펴봤는데, 이 과정을 브라우저에서 직접 확인해 볼 수 있다. Performance 패널에서 아무 사이트나 검사한 후, 메인 스레드의 작업을 살펴보면 작업 이름이 Parse HTML, Layout, Paint 라고 되어 있는 것을 볼 수 있다. 이 작업이 바로 지금까지 살펴본 브라우저 렌더링 과정이다.
차트를 보다 보면 회색 세로 점선을 볼 수 있는데, 이것은 브라우저가 화면을 갱신하는 주기이다. 브라우저는 화면을 최대 60번 그리게 되는데, 화면을 그리는 시점이 바로 점선으로 된 시점이다. 만약 화면이 전부 그려진 후, 애니메이션처럼 일부 요소의 스타일을 변경하거나 추가, 제거하면 어떻게 될까? 이런 경우 주요 렌더링 경로에서 거친 과정을 다시 한 번 실행하면서 새로운 화면을 그리는데, 이것을 리플로우(Reflow) 또는 리페인트(Repaint) 라고 한다.
한 가지 예를 들어 보겠다. 처음 화면이 모두 그려진 후, 자바스크립트로 인해 화면 내 어떤 요소의 너비와 높이가 변경되었다고 가정해보자. 그러면 브라우저는 해당 요소의 가로와 세로를 다시 계산하여 변경된 사이즈로 화면을 새로 그릴 것이다.
앞서 살펴본 주요 렌더링 경로에 대입해 보면, 먼저 요소의 스타일이 변했으니까 CSSOM 을 새로 만들어야 한다. 그러고 나서 변경된 CSSOM 을 이용하여 새로운 렌더 트리를 만든다. 그리고 요소의 가로와 세로를 변경했으니, 레이아웃 단계에서 당연히 요소의 크기와 위치를 다시 고려해야 한다. 그 다음 변경된 화면 구성에 알맞게 색을 칠하고(페인트) 분할된 레이어를 하나로 합성(컴포지트)할 것이다. 이를 리플로우(Reflow) 라고 한다.
리플로우는 주요 렌더링 경로의 모든 단계를 모두 재실행한다. 그렇기 때문에 브라우저 리소스를 많이 사용한다.
리플로우 : position, display, width, float, height, font-family, top, left, font-size, font-weight, line-height, min-height, margin, padding, border 등
리페인트 : background, background-image, background-position, border-radius, border-style, box-shadow, color, line-style, outline 등
또 다른 경우를 예로 들어 보겠다. 이번에는 한 요소의 가로, 세로와 같은 레이아웃 관련 속성이 아니라 글자 색(color) 이나 배경 색(background-color)등, 색상 관련 속성이 변경되었다고 가정해보자.
처음에는 스타일 속성이 변경되었기 때문에 CSSOM 이 새로 생성될 것이고, 렌더 트리도 새로 만들어질 것이다. 하지만 레이아웃 단계는 실행되지 않는다. 왜냐하면 지금 변경된 내용은 색상에 관련된 내용이지 요소의 위치나 크기에 영향을 주는 내용은 아니기 때문이다. 그렇게 레이아웃 단계를 건너뛴 뒤, 색을 입히는 페인트 단계, 레이어를 합성하는 컴포지트 단계를 거친다. 이것을 리페인트라고 한다.
리페인트 작업은 레이아웃 단계를 건너뛰었기 때문에 리플로우 작업보다는 조금 더 빠를것이다. 하지만 리페인트 역시 거의 모든 단계를 거치기 때문에 리소스를 꽤 잡아먹는다.
정리해 보면, 요소의 상태 변화가 일어나면 리플로우나 리페인트 과정을 거치는데, 이런 과정은 브라우저의 리소스를 많이 잡아먹기 때문에 결국 화면을 새로 그리는 것이 느릴 수 밖에 없다는 뜻이다.
다행히 리플로우와 리페인트를 피하는 방법이 있다. 바로 transform, opacity 와 같은 속성을 사용하는 방법이다. 이런 속성을 사용하면 해당 요소를 별도의 레이어로 분리하고 작업을 GPU 에 위임하여 처리함으로써 레이아웃 단계와 페인트 단계를 건너뛸 수 있다. 이것을 하드웨어 가속이라고 한다.
하드웨어 가속은 CPU 에서 처리해야 할 작업을 GPU에 위임하여 더욱 효율적으로 처리하는 방법을 말한다. GPU 는 애초에 그래픽 작업을 처리하기 위해 만들어진 것이므로 화면을 그릴 때 활용하면 굉장히 빠르다.
특정 요소에 하드웨어 가속을 사용하려면 요소를 별도의 레이어로 분리하여 GPU 로 보내야 하는데, 앞서 얘기한 것처럼 transform 속성과 opacity 속성이 이 역할을 한다. 분리된 레이어는 GPU 에 의해 처리되어 레이아웃 단계와 페인트 단계 없이 화면상의 요소의 스타일을 변경할 수 있다. 따라서 리플로우와 리페인트를 일으키는 width, height, color 등의 속성이 아닌 transform, opacity 속성을 이용한 애니메이션 성능이 더 좋을 수 밖에 없다.
이 글에서는 브라우저 렌더링 과정과 그에 따른 성능 최적화 방법에 대해 알아보았다. DOM과 CSSOM의 결합으로 시작되는 렌더링 파이프라인부터, 화면을 구성하고 최종적으로 그려지는 과정까지의 모든 단계를 살펴보았다. 특히, 리플로우와 리페인트가 브라우저 리소스에 미치는 영향과 이를 최소화하기 위한 하드웨어 가속 방법에 대해 다루었다.
성능 최적화를 위해 우리가 작성하는 코드가 브라우저에 어떤 부담을 주는지 이해하는 것이 중요하다. 가능한 한 리플로우와 리페인트를 피하는 방법을 고민해야 한다. 애니메이션을 구현할 때는 transform과 opacity 같은 속성을 사용하여 성능을 최적화하고, 브라우저가 보다 효율적으로 화면을 그릴 수 있도록 도와주는 것이 필요하다.
이 글이 브라우저 렌더링 과정에 대한 이해를 높이고, 실무에서 더 나은 성능을 위한 코드를 작성하는 데 도움이 되기를 바란다. 최적화된 코드가 사용자의 경험을 얼마나 향상시킬 수 있는지 깨닫고, 이를 통해 더 빠르고 부드러운 웹 애플리케이션을 만드는 데 기여할 수 있기를 기대한다.