브라우저는 어떻게 렌더링 되는가 (DOM , CSSOM , Render Tree)

ChoiYongHyeun·2024년 2월 6일
11

브라우저

목록 보기
1/16
post-thumbnail

나는 두달 동안 HTML , CSS , JS 를 배우고 HTTP , 네트워크 , OS 에 대한 강의를 수강하고

몇몇개의 토이프로젝트들을 만들어봤다.

하지만 이런 과정 속에서 브라우저는 어떻게 렌더링 되는가에 대한 깊은 고민을 안해봤던 것 같다.

그냥 어 ~ DOM 트리 만들고 CSSOM 만들고 Render Tree 만들어서 뿅 하고 띄워요 ~

이랬는데 단단하게 배우지 않다보니 Reflow , Repaint 에 대한 내용이 나오면 나중에 봐야지 하고 넘겼던 것 같다.

그래서 이 ! 번 ! 에 ! 좀 단단하게 공부해봤다.

2024/02/13 업데이트

지금 배워본 것은 아주 추상적인 내용이였다.
브라우저 렌더링 파이프라인 딥다이브
좀 더 깊은 이야기는 추가로 공부했던 해당 게시글을 참고하면 좋을 것 같다 !!


브라우저 렌더링 과정을 왜 알아야 할까 ?

브라우저가 얼마나 빠르게 렌더링이 되는가는 UX 와 매우 깊은 연관이 있다.

통계적으로 렌더링 까지 걸리는 시간이 3초 이상이 되면 사용자 이탈률이 높아진다는 내용이 있으며

사용자 이탈률은 수입과 직접적으로 관련있는 부분이기 때문에 UX 를 높이는 것, 브라우저 렌더링을 최적화 하는 것은 언제나 중요하다.

아마존은 페이지의 로딩 시간이 매출에 미치는 영향을 정확히 알고 있다. 이미 2008년부터 자체 연구를 통해 로딩이 0.1초 지연 될 때마다 판매가 1퍼센트 감소한다는 사실을 발견한 것이다. 2012년 조사에서는 로딩이 1초 길어질 경우 연간 자그마치 1.6조달러의 손실이 발생할 것으로 산출했다. - 박정준, ⟪나는 아마존에서 미래를 다녔다⟫, 한빛비즈


브라우저 구성 요소

Understanding the Role of Rendering Engine in Browsers

브라우저는 다음과 같은 계층적 구조로 이뤄져있다.

  • User Interface

유저에게 시각적인 요소들과 함께 상호작용 하는 웹 페이지를 의미한다.
페이지 뿐이 아니라 주소창, 홈 버튼, 뒤로가기 버튼 등 기능을 하는 토글들도 여기에 포함된다.

  • Browser Engine

core component of every web browseruser interfacerendering engine 을 잇는 다리 역할을 한다.
user interface 에서 오는 쿼리문들을 Rendering Engine 에게 적절하게 분배한다.

  • Rendering Engine

이름에서 알 수 있듯이 사용자의 화면에 HTML or XML, CSS , JS 를 적절히 해석하여 최종 결과물을 생성하고 User Interface 에 띄우도록 한다.

렌더링 엔진은 사용하는 브라우저마다 다르다.

브라우저 별 렌더링 엔진의 종류
Google - Blink
IE - Trident
FireFox - Gecko
Crome for IOS - WebKit

  • Networking

HTTP,HTTPS,FTP 등을 통해 네트워크를 관리한다.

  • JavaScript Interpreter

이름대로 자바스크립트를 적절하게 Parsing 하고 실행한다.

자바스크립트 인터프리터의 최종 결과물이 생성되면 user Interface 에 최종 결과물이 렌더링 된다.

  • UI Backend

UI BackendUser InterfaceOS 관련 내용이 저장되어 있는 관리소이다 .

  • Data Storage / Persistence

웹 브라우저는 쿠키같은 내용을 지역적으로 관리 할 필요가 존재한다.

WebSQL , IndexedDB , FileSystem 등의 메커니즘을 이용해 데이터를 임시적/영구적으로 저장한다.


렌더링 엔진이 하는 일

깊게 들어가기 전 브라우저 렌더링 과정을 한 눈에 보도록 하자

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body></body>
  <script src="script.js"></script>
</html>

어떤 브라우저에 들어갔고, 해당 브라우저에서 html 문서를 받아왔다고 가정해보자

맨 처음 렌더링 엔진은 받은 HTML 문서를 DOM Tree 자료구조를 만들기 위해 token 형태로 분해하고

만들어진 token 에서 문맥에 맞게 실행하고 HTML 태그들의 파싱 결과인 DOM Tree 를 만든다.

문맥에 맞게 실행한다는 것은 <link rel ='stylesheet'> 와 같은 link 태그를 만나면 주어진 주소로 GET 요청을 보내는 등과 같은 행위를 말한다.

DOM Tree 가 만들어진 후 CSS 파일도 파싱 후 트리 구조의 자료구조인 CSSOM (CSS Object Model) 을 만든다.

이 때 CSSOMDOM 과 관련된 스타일 속성들로 이뤄져있다.

이후 DOMCSSOM 을 합쳐 Render Tree 를 만들고

<script src = 'script.js'> 태그를 만나 script.js 에 대한 요청 , 파싱 , 실행을 통해 Render Tree 를 조작하여 새로운 Render Tree 를 만든다.

script.js 의 실행이 완료되면 결과적으로 만들어진 Render Tree 를 브라우저에 띄운다.

이 때

띄우기 전 Layout 단계를 거쳐 노드들의 적절한 크기와 위치를 조정하고

Paint 단계를 거쳐 노드들의 컨텐츠들을 브라우저에 픽셀 단위로 그려 띄운다.

이 때 Layout 단계가 한 번 더 일어나는 단계를 Reflow , Paint 단계가 한 번 더 일어나는 단계를 Repaint 라고 한다.

이제 대략적인 내용을 알았으니 딥다이브 해보자 슈류륙


DOM Tree 생성

해당 부분의 주 내용은 Kruno: How browsers work | JSUnconf 2017

Critcal rendering path - Crash course on web performance (Fluent 2013) 를 참고했다

Parsing

Parsing 이란 문자열로 이뤄진 어떤 표현식을 컴퓨터가 이해 할 수 있는 자료 구조 형태로 변경하는 것을 의미한다.

변경 할 때에는 문법적인 내용과 문맥적인 내용을 모두 지키며 자료구조로 변환해야 한다.

이러한 parsing 과정을 도식화 한 이미지로

HTML 문서는 사용자에게 도착했을 때는 encoding 되어 바이트 코드 형태로 존재한다 .

이를 적절히 decoding 시켜 문자열로 변환하고

해당 문자열들을 문맥적으로 적절히 토큰화 한다.

문맥적으로 적절히란 것은

<div> ... </div> 라는 태그가 있을 때 <div>Start Tag , </div>EndTag 로 두고 내부에 존재하는 태그들은 자식 태그로 간주할 수 있듯 말이다.

이런식으로 적절히 토큰화 시킨 토큰들과 관계에 따라 DOM Tree 를 생성한다.

부모, 자식 관계를 갖는 DOM Tree 라는 것에 주목해야 한다.

이러한 부모 자식 관계를 갖는 계층적 구조를 통해 우리는 더 빠르게 노드를 조작 할 수 있고

각 노드들의 위치와 크기는 부모 노드를 기준으로 하여 상대적으로 작성 할 수 있다.

이 부분은 Reflow 부분에서 한 번 더 기술하도록 한다.


CSSOM (CSS Obect Model)

CSSRender Blocking 을 유발할까 ?

Render Blocking 이란 브라우저가 페이지를 빠르게 렌더링 하지 못하게 지연시키는 행위를 의미한다.

밑의 예시를 통해 Render Blocking 이 일어나는 경우를 확인해보자

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body></body>
  <script src="script.js"></script>
</html>

브라우저 엔진이 HTML파싱 중 head 태그 안에서 <link rel = 'style.css' href = 'style.css'> 를 만나면

비동기적으로 style.css 에 대한 요청을 보내고 DOM 이 완성 될 때 까지 대기한다.

DOM 이 모두 생성되고 나면 style.css 에 대해 token => node => CSSOM 을 생성한다.

CSSOMDOM 의 모습과 상관 없이 오로지 style.css 에 적힌 텍스트를 기반으로

독립적으로 생성된다.

만약 HTML 파일에서 <div><p></p></div> 의 형태로 생긴 노드가 없더라도
style.css 에서 div > p {} 로 정의한다면 CSSOM 에는 해당 노드가 생성된다.

실제 개발자 도구의 Performance 에서 살펴보면 DOM 이 모두 생성된 후 Parse stylesheet 를 시행하는 모습을 볼 수 있다.

style.css 에 대한 요청이 body 태그보다 먼저 등장했는데도 말이다 !

이것만 보면 마치 style.cssRender Blocking 을 유발하지 않는 것 처럼 보인다.

이는 위에서 말했듯 head 태그 내부에 존재하는 <link style.css ..> 에서 요청은 비동기적 , 파싱은 DOM 생성 이후에 하도록 브라우저 엔진이 설계 되었기 때문이다.

하지만 이번엔 <link style.css ...>body 태그 내부에 넣어보자

..
  <body>
    <div>1</div>
    <link rel="stylesheet" href="style.css" />
    <div>2</div>
  </body>
..

이런식으로 되면 어떻게 될까 ?

이번엔 DOM 을 만들던 중 CSSOM 을 먼저 만들고 DOM 을 마저 완성했다.

이처럼 CSS 파일은 DOM 을 만드는 과정을 멈추게 하는 Render Blocking 요소이다.

하지만 우리는 CSSRender Blocking 요소라는 것이 와닿지 않았는데

이는 우리가 관례적으로 style.css 파일을 head 태그 내부에서 불러왔기에 브라우저 엔진이

관례적으로 DOM 트리 생성 이후 CSS 파일을 파싱해왔기 때문이다.

브라우저는 해당 방법을 통해 CSSRender Blocking 을 방지한다.

CSSOM 의 구성

CSSCadacasting Style Sheet 의 줄임말이다. Cadacasting 은 폭포라는 의미로

마치 폭포처럼 부모 노드의 style 속성이 하위 노드로 전파되기 때문이다.

위 이미지에서 볼 수 있듯 body 태그에 설정된 font-size : 16px 속성이 하위 태그인 p , span 태그까지 전파되고 있다.

이 때 하위 노드에서 부모 노드에서 설정한 속성을 오버라이딩 하면

본인 노드에서 설정한 스타일 속성이 우선적으로 선택된다.


Render Tree

이미지에서 좌측이 CSSOM , 우측은 DOM + CSSOM 으로 만들어진 Render Tree

DOMCSSOM 이 생성되면 두 노드를 결합해 Render Tree 를 만든다.

이러한 Render Tree 가 자바스크립트 엔진에서 조작할 DOM 에 해당 되며

User Interface 에 띄워지는 내용들이 해당 Render Tree 이다.

Render TreeDOM 의 형태를 순회하며 각 노드들이 CSSOM 에 정의된 관계 형태를 따르는지를 확인하며 설정한다.

Render Tree 를 빠르게 만들고 싶으면 CSSOM 을 복잡하게 만들지 마세요


??? : "복잡하게 만들지 마라 "

Minimizing browser reflow

예를 들어 이미지에는 없지만

CSSOM 에서 p > span 의 스타일 중 하나가 display : none 으로 되어있다고 해보자

그리고 만약 CSSOM 에서 그냥 body span 의 스타일 중 하나가 display : block 로 되어있다고 해보자

그럼 렌더링 엔진은 Render Tree 를 만들기 위해 DOM을 순회하던 중 span 태그를 만나면

해당 span 태그의 부모 태그가 p 인지 아닌지를 확인해야 한다.

그런 관계에 따라 적용되는 스타일이 다르기 때문이다.

차라리 원하는 스타일이 있다면 class , id 와 같은 선택자를 이용하는 것이 훨씬 CSSOM 의 깊이를 낮춰 줄 것이며

Render Tree 를 만들기 위해 DOM 을 순회 할 때 복잡한 부모 태그와의 관계를 확인 할 필요 없이 그저 선택자만 한 번 확인하면 되기 때문에 훨씬 빠르다.

article > div > div > p > span 이런식으로 생긴 CSS selector 로 스타일을 정의해놨다 생각해보자
그러면 span 태그를 만날 때 마다 부모 태그들을 슉슉 다시 확인해야 한다.

이런식으로 Render Tree 가 먼저 완성되면 브라우저에 렌더링 된다.


Javascript 는 언제 실행되나요

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="script.js"></script>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <div class="box1">1</div>
    <div class="box2"></div>
  </body>
</html>

자 이번에는 html 파일에서 script.js 파일을 style.css 보다 앞에 위치 시켜줬다.

그러면 script.js 파일의 실행이 style.css 보다 먼저 될까 ?

그렇습니다

script.js 파일 또한 DOM 생성을 막는 Render Blocking 요소이다.

그렇기에 우리는 일반적으로 다음과 같은 구조로 HTML 문서를 구성해야 한다.

<!DOCTYPE html>
<html lang="en">
  <head>
    ..
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
  </body>
  <script src="script.js"></script>
</html>

body 태그 이후에 script.js 파일을 실행시킴으로서 Render Tree 가 모두 완성 된 후

요청과 실행을 하게 만들어주먼 얻는 이점이 다음과 같다.

  • DOM , CSSOM 의 조합으로 만들어진 Render Tree 를 먼저 브라우저에 렌더링 하여 더 빠르게 페이지를 띄워줄 수 있다.
  • Render Tree 에 대해 조작함으로서 html , css 파일을 만들며 기대했던 것과 같은 일관된 결과를 가져 올 수 있다.

Render Tree 가 먼저 완성되면 브라우저에 렌더링 되고 script.js 파일에서 Render Tree 조작이 발생하면 한 번 더 렌더링 한다.


렌더링 하기 위한 계산 Layout

부모 자식 관계를 갖는 계층적 Render Tree 가 생성되었다면 Render Tree 를 이제

브라우저에 렌더링 해야 한다.

리플로우란 계층적 관계를 기반으로 웹 페이지에서 Render Tree 의 각 Node 들이 위치할 레이아웃과 위치를 계산하는 프로세스를 의미한다.

meta name = 'viewport'

HTML 문서의 head 태그에 존재하는

<meta name="viewport" content="width=device-width, initial-scale=1.0" />

태그를 본 적 있을 것이다.

해당 meta 태그는 초기 레이아웃 및 크기 조정 동작을 제어하는데 중요하다.

width= device-width 는 패이지의 뷰포트가 사용자 디바이스의 너비가 되도록 지시하고

initial-scale = 1.0 은 페이지의 확대/축소 수준이 100% 가 되도록 설정한다.

해당 이미지는 일본의 어떤 프로그래머가 파이어폭스의 렌더링 엔진인 Gecko 가 레이아웃 하는 과정을 찾아낸 영상이다.
Gecko Reflow Visualization - mozilla.org

이후에는 Render Tree 에 정의된 위치 및 크기 관련 속성을 통해 각 노드들이 브라우저에서 렌더링 될 위치들을 계산한다.

위치 및 크기를 계산한다는 것은 부모 자식 관계에 따른 상대적 위치를 결정한다는 것이고 혹은 부모 태그 너비의 50% 라고 적힌 수치를 px 단위로 변경하여 정확히 해당 노드가 렌더링 될 공간을 준비한단 것이다.


노드들이 위치할 공간들도 알고 있으니 이제 렌더링 하자 Paint

Paint 단계에서는 Render Tree 에 존재하는 속성들을 이용해 브라우저에 렌더링 한다.

paint 단계에서는 레이아웃이나 위치를 변경하지 않고 화면 요소의 시각적 모양을 업데이트 한다.

시각적 모양을 업데이트 한다는 것은 Render Tree 에 있는 텍스트 요소를 적는 것일 수도 있고

색상을 변경하거나, 테두리를 그린다거나 등의 행위를 의미한다.

이러한 Paint 단계가 모두 완료되면 우리는 비로소 브라우저에서 페이지를 볼 수 있게 된다.


진짜 수많은 게시글과 유튜브들을 봤는데 개인적으로

Kruno: How browsers work | JSUnconf 2017 가 가장 도움 됐던 것 같다.
이후는 모두 a 태그로 적기 귀찮아 출처만 기술한다. :)
https://dev.to/gopal1996/understanding-reflow-and-repaint-in-the-browser-1jbg
https://www.browserstack.com/guide/browser-rendering-engine
https://beomy.github.io/tech/browser/browser-rendering/
https://web.dev/articles/critical-rendering-path/render-tree-construction?hl=ko
https://onlydev.tistory.com/9
https://www.youtube.com/watch?v=CHwwSgKfXDE

profile
빨리 가는 유일한 방법은 제대로 가는 것이다

0개의 댓글