본문 바로가기
Woowa Techcourse/Missions

브라우저 렌더링 과정을 알아보자!

by mingule 2021. 8. 26.

들어가기

Babble 페이지를 제작하면서, 프론트엔드 팀은 페이지 곳곳에 여러가지 애니메이션을 넣어 유저에게 재미를 주고자 했다. 우리 사이트에서 제공하는 페이지나 기능이 여타의 사이트들에 비해 많은 편이 아니었기에, 제공하는 디테일에 신경쓰고 싶었다. 어떻게 보면 정신없어 보이기도 하지만, 유저가 페이지를 단순히 이용한다기보다는 페이지와 티키타카 한다는 느낌을 받을 수 있으면 좋겠다는 생각이었다. 

 

트위치와 같은 게임 관련 사이트들에서도 둘러보다보면 유저에게 재미를 주는 애니메이션을 곳곳에서 발견할 수 있었다. 그렇게 여러 사이트들을 둘러보다보니, 궁금한 점이 생겼다. 트위치만 해도 영상을 메인으로 하는 사이트이기 때문에 일단 사이트의 로딩에 대한 최적화가 필요할텐데, 어떻게 애니메이션과 자동재생 영상들을 이렇게 많이 넣어두는데도 유저가 문제없이 사이트를 이용할 수 있는걸까? 

 

우리 사이트에서도 유저가 큰 문제없이 애니메이션을 즐기며 이용할 수 있었으면 좋겠다는 생각이 들었고, 그에 따라 아래에 우리가 고민했던 부분에 대해 적어보고자 한다. (스포) 사실 우리가 직면했던 문제는 브라우저의 동작 원리에 관한 내용은 아니었지만, '혹시 Reflow, Repaint에 관한 문제는 아닐까?' 라는 의심을 계속 했었기 때문에 살짝쿵 적어본다.

 

💩 왔다갔다하는 못생긴 애니메이션 

뭣이 문제야?!

Babble 페이지에서 게임을 골라 방 목록 페이지에 들어오면, 랜덤한 닉네임이 발급되면서 '닉네임을 변경해주세요' 라는 alert가 뜬다. 닉네임 위에 둥둥 떠있는 애니메이션을 상상하고 구현했고, 잘 동작했다. 하지만 문제는 처음 페이지에 들어왔을 때 발생했다. 자꾸 아래처럼 닉네임을 가렸다가 제자리를 찾는 모습이 보였다. 굉장히 거슬렸고, 우리는 이를 해결하기 위한 방법을 찾아보기로 했다.

 

처음에 우리는 absolute, top, left 속성을 이용해 animation이 동작하도록 했다. 그런데 위와 같이 움직이는 애니메이션을 보고, '아 혹시 Reflow가 일어나는 과정이 느려서 우리 눈에까지 이렇게 보이는걸까?' 라고 고민했다. 그래서 transform 속성으로 바꾸었는데, 그래도 문제가 일어나긴 하더라! 우리가 어떤 점을 놓치고 있는 건 아닐지, 고민하면서 브라우저의 동작 원리를 다시 한 번 정리해보았다. 

🌍 브라우저는 어떻게 동작할까?

뜬금없이 브라우저의 동작 원리에 대해 설명해보려고 한다. 먼저 브라우저가 어떻게 내부적으로 돌아가는지 알아야 어떤 부분에 있어 성능 상 문제가 생기고 있는지 그리고 그것을 개선해 나갈 수 있을 것인지 대략적인 부분이 나올 수 있기 때문이다. 

 

브라우저란 무엇일까? 

브라우저란 웹 페이지, 이미지, 비디오 등의 컨텐츠를 수신하고 전송, 그리고 유저에게 표현해주는 소프트웨어이다. 더 풀어서 설명해보자면 브라우저는 유저가 선택한 내용을 서버로 요청해 서버로부터 데이터를 받고, 자체적인 렌더링 엔진을 사용해 이를 사용자가 보기 쉽게 텍스트나 이미지, 비디오 등으로 변환한다. 이렇게 변환된 데이터는 HTML 코드로 작성되어 있고, 브라우저는 이 HTML 코드를 읽고 화면을 사용자에게 표시해준다.

 

우리가 흔히 인터넷에 접속할 때 사용하는 Chrome, Safari, Firefox, Internet Explorer 등을 브라우저의 예로 들 수 있겠다. 이 브라우저들은 서로 다른 데이터 해석 방식을 가지고 있다(프론트엔드 개발자를 귀찮게 한다). 그래서 예전에는 같은 코드를 써도 브라우저마다 다른 화면을 보여주니 혼란이 많았는데, 웹 표준 명세를 정함에 따라 최근에는 대부분의 브라우저가 이를 잘 따르고 있어 요즘에는 큰 호환성 문제가 생기지 않는다. 그래도 완벽하지는 않기에, 개발자들도 웹 표준을 잘 지키면서 개발하는 것이 중요하다.

 

브라우저의 로딩 과정

브라우저는 유저가 요청한 사항에 대해 서버에 요청하고, 응답을 받아 해석해 콘텐츠를 화면에 띄운다. 이러한 일련의 과정을 브라우저의 로딩 과정이라고 한다. 이 과정을 5단계로 설명해보겠다. Chrome 개발자도구의 Performance Tap에서 아래의 단계를 살펴볼 수 있다.

 

1. Parsing (파싱) - DOM Tree, CSSOM Tree 구성

브라우저는 서버에서 응답으로 받은 데이터를 HTML로 변환해 해석하면서 DOM Tree를 구성한다. 그렇게 구성하는 와중에 JS 파일, Style 파일, 이미지 파일 등을 마주치게되면 리소스를 요청하고 다운로드하게 된다. 만약 Style 파일이 포함되어 있으면 CSSOM Tree 구성 작업도 같이 진행된다. 아래의 코드와 사진을 통해 쉽게 이해할 수 있다.

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Critial Path: Hello world!</title>
  </head>
  <body>
    <div style="width: 50%">
      <div style="width: 50%">Hello world!</div>
    </div>
  </body>
</html>

https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction?hl=ko
출처 : https://developer.mozilla.org/ko/docs/Learn/CSS/First_steps/How_CSS_works


2. Style (스타일 단계)

스타일 단계에서는 1번 Parsing 단계에서 생성된 DOM, CSSOM Tree들을 가지고 각각의 Style을 매칭시켜준다. 두 Tree를 합치는 과정이다. 이렇게 매칭시킨 친구들을 가지고 아래와 같은 Render Tree를 구성한다.

https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction?hl=ko

 

3. Layout/Reflow(레이아웃 단계) - 정확한 계산으로 화면에 그려질 Render Tree 만들기

레이아웃 단계는 이렇게 2번 과정을 통해 만든 Render Tree를 화면의 어디에 배치해야 할 것인지에 대해 계산하는 과정이다. 각 Node가 정확히 화면의 어느 부분에 위치해야 할 것인지, 그리고 그 크기는 어떻게 되는지 루트부터 각 Node를 순회하면서 계산한다. 그리고 계산한 정확한 pixel 값을 Render Tree에 반영한다. 만약 크기를 %로 지정해 두었더라도 pixel 값으로 계산된다. 

4. Painting (페인트 단계) - 화면에 그리기

4번의 레이아웃 단계에서 계산된 값을 Render Tree의 각 Node를 실제 화면에 전달한다. 이 때, Node들을 한꺼번에 하나의 레이어로 화면을 만드는 것이 아니라 겹겹의 레이어로 만들어 화면에 그린다. 이렇게 레이어를 분리해두면 다시 Paint 해야하는 일이 발생했을 때 모든 레이어가 아니라 하나의 레이어만 Paint해도 된다는 장점이 있다.

5. Composite (합성 단계)

Composite 단계는 이렇게 여러 레이어로 나누어진 픽셀값들을 우리가 실제로 보는 화면처럼 합성해주는 단계이다. 4번의 Painting 단계에서 그려진 레이어들을 순서에 맞추어 합성해 유저가 보는 화면을 만든다.

 

 

 

Reflow(Layout) / Repaint

그럼 매번 어떤 변경이 일어나더라도 위의 브라우저 렌더링 과정을 1-5번까지 다 겪게 되는 걸까? 답은 그렇지 않다. 

폰트나 배경 색상을 바꾼다던가, 그림자 효과를 넣는 등의 레이아웃 크기나 위치에 영향이 가지 않아 다시 계산해야 할 필요가 없는 속성들을 사용하면 위치를 상세히 계산하는 Layout 과정을 건너뛰고 Paint 과정부터 시작하게 된다. Layout 단계에서 대부분의 연산작업이 이루어지기 때문에, 이 Layout 단계를 건너뛰기만 해도 렌더링 속도가 크게 개선된다. 

 

아래는 Reflow가 발생되는 대표적인 속성들이다. 아래의 속성들을 사용하면 다시 화면을 계산해야한다.

position width height left top text-align
right bottom margin padding border vertical-align
border-width clear display float font-family white-space
font-size font-weight line-height min-height overflow ....

아래는 Repaint가 발생되는 대표적인 속성들이다. 계산이 다시 일어나지 않기 때문에 성능 상 이점이 있다.

background background-image background-position background-repeat
border-style border-radius box-shadow text-decoration
visibility color outline outline-color
outline-width background-size line-style outline-style

 

No Layout / No Paint

그럼 Layout이나 Paint를 꼭 거쳐야만 할까? 그건 아니다. 

Transform, Opacity, Cursor, Orphans, Perspective 등의 속성은 Reflow나 Repaint 과정을 거치지 않고 Render된다.

(위 내용은 브라우저 엔진마다 조금씩 다르기 때문에 브라우저 별로 확인한 후에 사용하는 편이 좋다.)

그렇기 때문에 가장 성능상 뛰어나다. 화면 상에 무언가를 위치시켜야 한다고 했을 때, left, top과 같은 속성을 사용하는 것보다는 transform 속성을 사용하는 편이 좋다. 이 링크(CSS Triggers)에 어떤 속성들이 어떻게 화면상 그려지는 지 잘 나와있기 때문에 참고하면서 속성을 사용한다면 성능상 더 좋은 페이지를 만들 수 있을 것이다. 

 

그럼 도데체 뭐가 문제였던 거야?

웃기지만, 사실은 엄청 간단한 문제였다. 

'닉네임을 변경해주세요' 라는 alert에서 transform 속성을 넣어주었는데, 여기에서 사용하는 animation의 시작점과 다르게 만들어주어서 생긴 문제였다. 아래처럼 bounce transform 시작 위치를 우리가 지정해놓은 transform: translate(-50%, -160%)로 맞추어주니 제대로 동작했다. 다음부터는 조금 더 꼼꼼히 살펴 애니메이션을 만들어야겠다는 생각이 들었다.

 

마치며

생각하지 못한 문제로 브라우저의 동작 원리를 다시 한 번 정리해보는 시간을 가졌다. 화면을 우리가 생각하는 그대로 그리면서 유저가 사용하는 데 불편함 없이 만드는 방법은 생각보다 쉽지 않다는 것을 다시 한 번 느꼈다. 어렵지만 재밌당. 헤헿

 

 

 

https://ui.toast.com/fe-guide/ko_PERFORMANCE

 

성능 최적화

애플리케이션 성능 최적화는 앱과 웹에서 모두 중요하다. 최근 웹 애플리케이션은 Ajax 통신, 복잡한 UI 등 많은 기능을 담으면서 크고 무거워졌다. 무거워진 웹은 긴 로딩 시간 함께 사용자 경험

ui.toast.com

https://developers.google.com/web/fundamentals/performance/critical-rendering-path/constructing-the-object-model?hl=ko 

 

객체 모델 생성  |  Web Fundamentals  |  Google Developers

브라우저가 DOM 및 CSSOM 트리를 구성하는 방법을 학습하십시오.

developers.google.com

https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction?hl=ko 

 

렌더링 트리 생성, 레이아웃 및 페인트  |  Web  |  Google Developers

TODO

developers.google.com

 

 

댓글