프로그래밍 공부하기

브라우저 작동원리3 - 렌더링 본문

Web/[JS] FrontEnd

브라우저 작동원리3 - 렌더링

ihl 2021. 10. 19. 13:23

Drawing

  브라우저의 렌더러 프로세스가 서버로부터 받은 HTML, CSS, JS 등의 데이터를 조합하여 화면을 그리는 과정을 브라우저 렌더링이라 한다. 해당 과정은 렌더링 엔진(Webkit > Blink)과 엔진 내의 JS 인터프리터(V8)를 통해 이루어진다. 브라우저별로 렌더링 과정이 조금씩 다르다. 이 글에선 크롬 브라우저 렌더링 과정에 대해 정리해보고자 한다.

 

1. 데이터 파싱

  렌더러 프로세스는 우선 HTML, CSS와 같은 문서를 브라우저가 이해하고 사용할 수 있는 구조로 변환한다. 파싱은 크게 토큰화와 트리 생성 2단계로 이루어진다.

 

  토큰은 어휘 분석의 단위이다. 예를 들어 HTML 태그를 토큰화해보자. 우선 '<' 문자를 만나면 태그 열림 상태로 전환한다. 이후 a-z 문자를 만나면 시작 태그 토큰을 생성하고, 상태는 태그 이름 상태로 변하는데 이는 '>' 문자를 만날 때까지 유지한다. 이런 식으로 새로운 문자 탐색 > 상태 변경 > 처리(Stack에 쌓거나 모두 쌓이면 DOM 노드화)를 반복하며 토큰화를 수행한다.

 

  토큰화에 의해 발행된 각 노드는 트리 생성자에 의해 트리에 추가된다. HTML 파일은 DOM 트리, CSS 파일은 CSSOM 트리가 만들어진다. 이중 DOM은 브라우저가 HTML 문서를 표현하는 방버비며, 개발자가 JS를 통해 HTML과 상호작용할 수 있게 하는 데이터 구조이자 API이다.

 

  파싱 과정에서 네트워크 요청이 이루어지는 경우도 있다. 예를 들어 HTML 문서에 <img>나 <link> 태그가 있는 경우 토큰화 결과에서 혹은 DOM을 구성할 때 브라우저 프로세스의 네트워크 스레드에게 관련 요청을 보낸다. 리소스 요청을 미리 수행하고 싶다면 <link rel="preload">와 같은 속성을 주면 된다.

 

  HTML 문서 파싱 과정은 <script>태그를 만나면 중단되고, JS 코드를 실행한다. JS 코드가 DOM을 조작하여 구조를 변경할 수 있기 때문이다. 한편 <script> 태그의 JS 코드를 실행했는데 아직 DOM 구성이 완료되지 않아서 오류가 발생할 수도 있다. 따라서 <script> 태그를 HTML의 <body>가 끝난 후에 위치시키거나 defer 속성을 주는 것이 좋다.

 

  CSS는 DOM 트리를 변경하지 않으므로 문서 파싱을 기다릴 필요가 없으나, JS가 스타일 정보를 요청하는 경우 CSS 파싱이 완료되지 않았다면 JS가 잘못된 결과를 사용할 수 있다. 따라서 브라우저에 따라 CSS 파싱이 완료 전이라면 JS 실행을 중단한다.

 

2. Render Tree 생성

  Render Tree란 각 요소들의 스타일을 적용하고, 화면에 올바른 순서로 그려내기 위한 데이터 트리이다. DOM과 CSSOM을 합치는 작업을 Attachment라 하며, Attachment의 결과로 렌더트리가 생성된다. 동기적으로 수행되며, 렌더 트리의 노드를 렌더러라고 한다(webkit). 렌더러는 자신과 자식 요소를 어떻게 배치하고 그려야하는지 알고 있다.

 

  렌더러는 CSS 박스와 같은 사각형의 영역을 표현하며, 너비, 높이, 위치와 같은 기하학적 정보를 포함한다. display 속성에 따라 다른 유형의 렌더러가 생성된다. 예를 들어 display:none이라면 렌더러 트리에서 제외된다. 테이블과 폼의 경우 특별한 렌더러로 취급되어 높이, 너비 외에 다른 정보도 포함하고 있다.

 

  렌더 트리는 DOM 트리와 일대일 대응 관계는 아니다. display:none인 DOM 요소는 렌더 트리에 존재하지 않으며, select와 같은 요소는 여러 개의 렌더러로 분리된다. float, position 속성을 가진 요소는 DOM 트리와 동일한 위치에 요소가 존재하지 않을 수도 있다. CSS의 before와 같은 의사 클래스는 DOM 트리에는 포함되지 않지만 렌더 트리에 포함될 수 있다.

 

  CSS 스타일은 상속된다. 어떤 DOM 요소에 적용된 스타일이 없더라도 브라우저의 기본 스타일 시트 및 부모의 스타일 요소를 상속하여 스타일이 결정된다. 스타일 계산에서 메모리, 성능 문제가 발생할 수 있다. 이를 해결하기 위해 웹킷은 렌더러 간 스타일 객체를 공유하거나, 파이어폭스는 규칙 트리와 스타일 문맥 트리라는 두 개의 트리를 더 생성하기도 한다.

 

3. Layout(Reflow)

  레이아웃은 뷰포트를 기반으로 렌더러의 크기와 위치 정보를 계산하는 작업이다. 즉, Render Tree를 화면에 배치하는 작업이라고도 할 수 있다. Layout에서 em, vh와 같은 상대적 단위는 뷰포트에 맞춰 픽셀 단위로 변환된다.

 

  크롬 브라우저의 웹 페이지는 하나의 층이 아닌 여러 개의 레이어로 이루어져 있다. 화면이 여러 개의 레이어로 이루어져 있을 경우 화면의 어떤 요소의 CSS 속성이 변경되었을 때 전체가 변경되지 않고 해당 요소가 포함된 레이어만 변경하고 합성하는 것으로 처리할 수 있는 장점이 있다. 반면, 레이어가 너무 많으면 레이어를 저장하기 위한 메모리, 합성 작업을 위한 비용이 추가되므로 크롬은 레이어가 과도하게 많아지는 것을 막기 위해 경우에 따라 레이어를 생성하지 않기도 한다.

4. Paint

  이전까지 문서를 해석하고 데이터를 생성하는 작업이 주였다면, 이제부턴 실제 화면의 픽셀로 그리는 작업이 시작된다. 이를 위해 우선 화면을 어떤 순서로 어떻게 그릴지 계획하고, 정리된 데이터로 만든다. 정리된 데이터를 비트맵 이미지로 만드는 작업을 래스터라이징이라고 한다. 래스터 라이징은 그래픽 작업이므로 GPU의 도움을 받아 별도의 스레드에서 처리된다(브라우저에 따라 CPU가 할 수도 있다).

 

 

5. Composite

  비트맵 이미지화된 각 레이어들을 합치는 과정을 Composite라 한다. Composite 과정 역시 컴포지터 스레드라는 별도의 스레드에서 합성된다.

 

*성능 향상

  브라우저는 렌더링 과정의 성능 향상을 위해 Layer 분리 외에도 다양한 작업을 수행한다.

 

Invalidation

  렌더링 작업을 적게 하기 위해선 업데이트할 부분을 최소화하는 것이 좋다. Blink는 렌더링 파이프라인 단계 별로 유효성을 검사하여 업데이트가 필요한 부분의 데이터만 변경한다.

 

Layer Tiling

  레이어를 여러 개의 타일로 쪼개는 것을 타일링이라 한다. 예를 들어 레이어가 현재 뷰포트보다 크다면 현재 화면에 보이는 부분만 그리고, 나머지는 나중에 그려도 된다. 이렇게 레이어를 타일로 쪼개고 타일의 위치나 특성에 따라 먼저 혹은 나중에 그리는 것이다.

 

* Animation

  브라우저의 화면은 하나의 이미지가 끝이 아니다. 애니메이션 혹은 사용자 인터렉션으로 인해 화면의 요소가 언제든지 변경될 수 있다. 브라우저의 화면 업데이트가 매끄럽게 보이려면 모니터의 화면 갱신 타이밍(VSync)과 동일하게 시간에 화면 프레임을 생성하고 갱신해야 한다. 일반적으로 모니터의 주사율은 60Hz이다. 즉, 화면이 바뀐다면 1/60초에 위 과정(Rendering Pipeline)을 모두 수행해야 한다. 그러나 브라우저 렌더링이 재시작되면 60 프레임(0.016초)에 이들을 모두 수행하기 어렵고, 때문에 중간 과정을 생략하기도 한다. 이것이 애니메이션 버벅거림의 원인이 되기도 한다.

 

  화면 갱신을 빠르게 수행하기 위해 개발자가 할 수 있는 일은 무엇일까? 바로 화면의 요소 변경 시 최소한의 과정으로 화면이 업데이트되는 CSS 속성을 사용하는 것이다. 예를 들어 애니메이션을 위해 display 속성을 사용했다면 당연히 Layout 과정(Reflow)부터 다시 반복할 것이다. 그러나 display 대신 visibility 혹은 opacity를 사용한다면 브라우저에 따라 Paint 혹은 Composite 과정만 반복된다. csstriggers.com 에선 브라우저별로 각 CSS 속성을 변경했을 때 어떤 작업이 이루어지는 지에 대한 정보를 제공하고 있다.

 

  그 외에도 DOM 트리가 변경될 경우 하위 노드도 함께 변경되므로 변화가 필요한 요소는 DOM 구조 가장 끝단에 위치하도록 하는 것도 방법도 좋다. 또한 애니메이션이 발생하는 요소는 position: fixed 혹은 absolute로 지정한다면 이들은 다른 요소들과 다른 문맥(레이어)에서 실행되므로 컴포지터 스레드(GPU)의 도움을 받아 효율적으로 변경 가능하며, 다른 요소도 함께 reflow 되는 것을 막을 수 있다.

 


what happens when-KR : https://github.com/SantonyChoi/what-happens-when-KR

브라우저 동작 : https://d2.naver.com/helloworld/59361

모던 웹 브라우저 들여다보기 part3 : https://developers.google.com/web/updates/2018/09/inside-browser-part3

브라우저 렌더링 : https://ibocon.tistory.com/251?category=879638 

'Web > [JS] FrontEnd' 카테고리의 다른 글

sCU vs pureComponent vs React.memo  (0) 2021.12.16
Timer in Browser  (0) 2021.11.06
브라우저 작동원리2 - 페이지 이동  (0) 2021.10.19
브라우저 작동 원리1 - 브라우저 구조  (0) 2021.10.15
Attribute vs Property  (0) 2021.07.30
Comments