HTML, CSS, JavaScript 코드를 치면 뚝딱뚝딱 렌더링 해주는 브라우저의 렌더링은 대체 어떤 과정을 거쳐서 일어나는 걸까?
이를 알기 위해선 브라우저의 구성요소를 먼저 알아봐야 한다.
브라우저는 여러 영역들의 조합으로 이뤄져 있다.
위와같이 렌더링 된 페이지를 제외한 뒤로가기/앞으로 가기, 주소입력 등 페이지 렌더링을 제외한 부분을 User Interface라고 한다.
User Interface에서 사용자가 어떠한 행동을 취하면 해당 행동을 Rendering Engine에게 전달해주는 가교역할을 하는 부분이기도 하다.
웹 사이트를 그리는 엔진이다.
사용자의 행동에 의해서 User Interface을 통한 입력을 Browser Engine으로 요청받아 url을 받아와서 서버에 요청을 보내준다.
그 후, url에 해당하는 데이터를 받아서 파싱한 후에 브라우저에 렌더링 해준다.
크롬에서는 "blink"엔진을 사용한다.
웹 사이트를 호출할 때 사용한다.
즉, 웹 브라우저의 네트워크를 담당하는 부분이다.
개발자 도구에서 "네트워크 탭"항목에 들어가면 Networking의 활동을 볼 수 있다.
크롬에서는 대표적으로 "v8"엔진을 사용한다.
사용자와 소통하는 부분이다.
input, 마우스 움직임, click 등을 핸들링 하는 곳이다.
브라우저에 데이터를 저장할 수 있는 부분이다.
쿠키, 세션, 로컬스토리지 등이 있다.
주소입력창에 "naver.com"을 입력하고 엔터키를 누르면 어떤일이 일어날까?
여기서 주소입력창에 "naver.com"이라고 입력을 해서 요청을 보내도 해당 입력으로는 어떤 웹 사이트를 요청하는건지를 알 수가 없다.
도메인은 우리가 접근하기 쉽게 만든 하나의 별칭이고 실질적으로는 ip주소
에 의해서 해당 웹사이트에 요청을 할 수 있다.
즉, "naver.com"으로 입력했을 때 해당 도메인에 일치하는 ip주소
를 찾아서 요청을 하는 것이다.
"naver.com"도메인은 DNS(Domain Name System)서비스를 찾아가서 해당 도메인에 맞는 ip주소
를 찾아와서 요청을 보내는 것이다.
"naver.com"만 줬다고 해도 DNS에서 바로 찾아줄 수 있는건 아니다.
처음에는 루트 도메인에서 찾아보고 그 다음은 ".com"으로 찾아가서 해당 섹션에서 "naver.com"이 있는 지를 찾아보는둥 단계별로 나눠서 해당 도메인에 대한 ip주소
를 찾는다.
비유를 하자면 "위치에 따른 주소"라고 생각하면 된다.
택배 배송을 보낸다고 하면 "xx도 xx시 xx구 ...."와 같은 형태로 주소가 이뤄진거처럼 도메인도 위와같이 섹션에 맞게 단계별로 나눠서 해당 ip주소
를 찾는다.
그리고 찾은 주소는 해당 요청을 보낸 컴퓨터의 DNS Cache에 저장을 해 놓는다.
그리고 과거에 방문했던 곳으로 요청을 한 경우 DNS Cache에서 가져온다.
ip주소
를 찾았다면 해당 ip주소
로 필요한 데이터를 받기위한 요청을 보낸다.
요청에 의해서 받은 데이터는 눈으로 읽기쉬운 HTML태그들로 이뤄진게 아닌 이진법(0,1)로 이루어진 바이트 스트림 파일이다.
해당 파일을 브라우저는 읽기쉬운 파일로 인코딩 해준다.
보통 많이 쓰는 인코딩 방법은 UTF-8
이다.
코드 에디터에서 HTML파일을 생성해서 작성하다 보면 meta
요소 안에 charset="UTF-8"
을 정의하는걸 확인할 수 있다.
해당 HTML파일이 어떤걸로 인코딩 됐는지를 멘션해주기 위해서 정의하는 것이다.
요청으로 받은 바이트 스트림 파일을 인코딩 한 후에 바로 렌더링을 하는건 아니다.
브라우저도 인코딩 한 파일의 구성요소를 하나씩 다시읽어서 재구성해야 한다.
위와같은 작업을 토큰화
라고 한다.
토큰화의 과정은 말 그대로 한글자 한글자 읽는 과정이다.
<html>
<head>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
위와같은 HTML파일이 있다고 생각하면
위에처럼 한글자 한글자씩 읽는 것이다.
여기서 <
을 만나면 토큰의 시작임을 정의하고 >
을 만나면 토큰의 끝임을 정의한다.
즉, 요소의 여는 괄호와 닫는 괄호를 만날때마다 토큰의 시작과 끝을 정의해서 한쌍의 괄호를 하나의 토큰으로 정의한다.
토큰으로 구성된 HTMl파일의 요소들을 의미있는 단위로 재해석을 한다.
{
tag : div,
attribute : ...
}
위와같이 각 토큰에 대해서 객체(Object)형태로 만들게 된다.
각 토큰을 객체형태로 정의하여 해당 객체가 어떤 요소이고 어떤 속성을 가지는지 정의를 해주는 것이다.
위와같은 작업을 노드화
한다고 한다.
노드화
시킨 다음에는 서로 연관있는 요소들끼리 관계를 부여해줘야 한다.
HTML의 요소들은 구성에 따라서 부모와 자식의 관계로 서로 연결고리를 가지고 있다.
이런 연결고리를 맺어주는걸 Model
이라고 한다.
요소들끼리 연관된 관계를 맺어주고 나면 해당 결과물을 DOM Tree(Document Object Model Tree)
라고 한다.
HTML의 최종설계도인 DOM Tree
는 위와같은 과정을 거쳐서 만들어진다.
CSS의 렌더링 과정 같은 경우, HTML을 토큰화 하는 과정속에 link요소를 만나게 된다면 전에 요청을 보냈던 해당 ip주소
로 다시 요청을 보내서 CSS파일을 받아온다.
개발자 도구에서 "네트워크" 탭을 보면 loaclhost(HTML)을 불러온 다음에 link요소를 만나게 된다면 CSS를 요청하는걸 확인할 수 있다.
CSS도 HTML의 렌더링 과정과 똑같이 바이트 스트림 형태의 파일을 받아서 인코딩하여 캐릭터화 시킨다.
그 후, 캐릭터화 시킨 파일을 토큰화 한 다음에 노드화 시키고 CSSOM Tree
를 만들게 된다.
최종적으로 만들어진 CSSOM Tree
이다.
JavaScript가 실행되는 동안에는 DOM Parsing(DOM을 만드는 과정)
을 멈추게 된다.
JavaScript는 HTML요소를 컨트롤(요소를 추가하거나, 삭제하거나) 할 수 있는 메서드가 존재한다.
만약 해당 메서드가 실행된다면 HTML을 다시 그릴수밖에 없으므로 DOM Parsing
을 멈추고 JavaScript파일을 실행시키는 것이다.
위와같이 코드를 작성하면 button요소보다 script요소가 위에 있으므로 DOM Tree
가 구성되기 전에는 해당 요소를 찾지 못하여 에러가 나는 것이다.
그래서 script요소를 body요소에 맨 아래에 두는 이유이다.
물론 JavaScript가 DOM
생성을 막지 않게 하는 script옵션이 있다.
defer
옵션을 script요소에다가 정의하면 script요소가 위에 있어도 DOM
생성을 막지 않기 때문에 접근이 가능해진다.
defer
옵션은 DOM
이 모두 파싱되면 JavaScript를 실행한다.
async
옵션도 DOM
생성을 막지 않는 옵션 중 하나이다.
async
옵션은 비동기적으로 동작하므로 DOM
이 파싱되는 동안은 다운된다.
그럼 왜 CSS link는 항상 위에다 두는걸까?
CSS는 다행히도 DOM
의 생성을 막지 않는다.
DOM
과 CSSOM
이 완성되어서 합치면 Render Tree
가 만들어진다.
Render Tree
는 렌더링 하기 직전 최종 설계도인 것이다.
Render Tree
에서는 쓸데없는 노드들을 다 제거해준다.
예를 들면 head, meta요소 등등은 화면에 렌더링 되는 요소가 아니므로 해당 요소는 제거해준다.
또는 display: none
으로 스타일링된 요소도 제거해준다.
이제 만들어진 Render Tree
를 기반으로 해서 렌더링만 해주면 된다.
렌더링의 경우 3단계로 나눠진다.
Layout
Paint
Composite
Render Tree
가 완성되었다고 해도 바로 렌더링 할 수 있는건 아니다.
Render Tree
에 있는 노드들을 화면 어디에다가 정확히 그릴건지 정의해줘야 한다.
즉, Layout
은 각 노드들이 가지고 있는 px값들을 계산해준다.
계산해준 값들을 정의하여 Paint
단계로 넘어간다.
Paint
단계에서는 요소 하나하나를 실제로 그리는 단계이다.
한 페이지에 있는 모든걸 그리지 않고 Layout으로 나눠서 그려준다.
나눠서 그리는 이유는 한 페이지에 모든 요소들을 다 그렸는데 그 중 하나의 요소가 바뀐다면 해당 요소 때문에 전체를 다시 그려야 하는 상황이 발생해서 Layout을 나눠서 그리고 바뀐 요소가 있다면 해당 요소가 있는 Layout만 다시 그려주게 된다.
Layout으로 나눠서 그려진 요소들을 Composite
단계에서 합쳐서 보여준다.
Layout
~ Composite
단계는 웹 사이트를 이용하면 계속해서 반복되는 과정이다.
웹 사이트에서 어떠한 요소가 이벤트 핸들러에 의해서 바뀐다면 또 다시 Layout
을 계산해서 다시 그리는 과정을 반복한다.
그럼 Layout
이 반복 되는 순간은 언제일까?
Layout
을 바꾸는 과정은 많은 비용을 소모하게 된다.
Layout
이 바뀐다는건 다시 그려주는것이므로 웹 사이트 성능에 영향을 미친다.
그러므로 최대한 Layout
이 다시 계산되는 과정을 최소화 해주는게 좋다.
Repaint
는 Render Tree
를 기반으로 다시 Paint
단계를 반복하는걸 말한다.
주로 배경 이미지나 텍스트 색상, 그림자 등을 변경했을 때 Repaint
가 일어난다.
Reflow
는 Layout
이 다시 계산되는 현상으로 Reflow
가 일어나면 필연적으로 Repaint
가 발생한다.
주로 요소의 위치나 크기가 변하거나 브라우저가 리사이징 될 때 발생한다.
Recomposite
는 Layout
이나 Paint
단계가 다시 발생하지 않고 레이어의 합성만 일어나는 경우이다.
위 사이트는 Reflow, Repaint, Recomposite
를 발생시키는 CSS트리거를 설명해놓은 사이트이다.
아주 긴 시간동안 브라우저가 HTML, CSS, JavaScript를 렌더링 하는지에 대해서 알아봤다.
어떤식으로 렌더링 하는지에 대해서 얕은 지식으로 해당 개념을 알고 지냈는데 자료를 찾아보니 CSS의 성능적인 요소가 생각보다 이슈가 발생할 수 있다는걸 알았고 지금 개인프로젝트로 진행하는 랜딩 페이지에서 해당 개념은 뺄수가 없는 개념이라고 생각한다.
이번 개인프로젝트에서는 css를 생각없이 쓰지 않고 최대한 성능 이슈가 발생안하도록 구성해야 할거 같다.
https://www.youtube.com/watch?v=sJ14cWjrNis&list=LL&index=2
https://www.youtube.com/watch?v=Mqh13dNI8jc&list=LL&index=1&t=867s