helpful blog
브라우저 렌더링 과정 본문
자바스크립트는 웹 브라우저에서 동작하는 언어인데, 구글의 V8 자바스크립트 엔진으로 빌드된 자바스크립트 런타임 환경인 Node.js의 등장으로 웹 브라우저를 벗어나 서버 사이드 애플리케이션 개발에도 사용할 수 있는 범용 언어가 되었다.
웹 브라우저에서 동작하는 자바스크립트는 HTML과 CSS와 함께 실행된다. 따라서 브라우저의 렌더링 환경을 이해하면 더 효율적인 자바스크립트 프로그래밍이 가능하다.
브라우저 렌더링 과정은 간략하게 다음과 같이 이루어진다.
- 브라우저는 서버에 리소스를 요청하고 응답받는다.
- 브라우저 렌더링 엔진은 응답된 HTML 파일을 한 줄씩 순차적으로 읽어 들이면서 HTML을 파싱하여 DOM을 생성한다.
- CSS 링크를 만나면 DOM 생성을 일시 중단하고, 서버에 파일을 요청하고 응답받아 CSS를 파싱하여 CSSOM을 생성한다.
- 자바스크립트를 만나면 작업을 일시 중단하고, 서버에 파일을 요청하고 응답받으면 제어권이 자바스크립트 엔진에게 넘어간다. 자바스크립트 코드를 파싱하여 바이트코드로 변환하고 이를 실행한다.
- DOM과 CSSOM을 결합하여 렌더 트리를 만든다.
- 렌더 트리를 기반으로 레이아웃을 계산한다.
- 브라우저 화면에 페인팅한다.
URL 입력
- 브라우저 주소창에 https://google.com 을 입력하고 엔터 키를 누르면, URL의 호스트 이름이 DNS를 통해 IP 주소로 변환되고 이 IP 주소를 갖는 서버에게 요청을 전송한다.
- 이때 루트 요청이 google.com 서버로 전송되고 서버는 암묵적으로 index.html을 응답한다.
HTML 파싱과 DOM 생성
응답된 HTML 문서는 순수한 텍스트이므로 이를 브라우저가 이해할 수 있는 자료구조로 변환하여 메모리에 저장해야 한다. 따라서 HTML 문서를 파싱해서 브라우저가 이해할 수 있는 자료구조인 DOM(Document Object Model)을 생성하는 것이다.
- 서버는 브라우저가 요청한 HTML 파일을 읽어 들여 메모리에 저장한 다음 메모리에 저장된 바이트(2진수)를 인터넷을 경유하여 응답한다.
- 브라우저는 바이트(2진수) 형태의 HTML 문서를 받아서 meta 태그의 charset 어트리뷰트에 의해 지정된 인코딩 방식(예 : UTF-8)으로 문자열로 변환한다.
- 문자열로 변환된 HTML 문서를 읽어 들여 문법적 의미를 갖는 코드의 최소 단위인 토큰들로 분해한다.
- 토큰들의 내용에 따라 객체로 변환하여 각 노드들을 생성한다. (문서 노드, 요소 노드, 어트리뷰트 노드, 텍스트 노드)
- HTML은 요소 간의 부자 관계인 중첩 관계를 갖는데, 이를 반영하여 모든 노드들을 트리 구조로 구성하여 DOM을 만든다.
CSS 파싱과 CSSOM 생성
렌더링 엔진은 HTML 문서를 처음부터 한 줄씩 순차적으로 파싱하여 DOM을 생성해 나가는데, CSS를 로드하는 link 태그나 style 태그를 만나면 DOM 생성을 일시 중단한다.
그리고 link 태그의 href에 지정된 CSS 파일을 서버에 요청하고 응답받으면, HTML과 동일한 파싱 과정을 거쳐 CSSOM을 생성하고 작업이 완료되면 다시 DOM 생성을 이어나간다.
- 서버로부터 응답된 CSS 파일을 바이트 → 문자 → 토큰 → 노드 → CSSOM 의 과정을 거쳐 CSSOM을 생성한다.
CSSOM은 CSS의 상속의 개념을 반영하여 생성되는데, body 요소에 font-size가 적용되어 있다면 하위의 모든 요소에도 적용된다.
body {font-size: 12px;}
ul {list-style-type: none;}
자바스크립트 파싱
렌더링 엔진은 HTML 문서를 한 줄씩 순차적으로 파싱하다가 자바스크립트 파일을 로드하는 script 태그를 만나면 DOM 생성을 일시 중단한다.
script 태그의 src에 정의된 자바스크립트 파일을 서버에 요청하여 응답받으면 자바스크립트 코드를 파싱하기 위해 자바스크립트 엔진에게 제어권을 넘긴다.
자바스크립트 파싱이 끝나면 렌더링 엔진으로 다시 제어권을 넘기고 DOM 생성을 이어나간다.
만약 생성되지 않은 DOM을 조작한다면 에러가 발생할 수 있다. 따라서 body 요소 아래에 자바스크립트를 위치 시키거나 DOM 생성이 완료된 시점에 자바스크립트가 실행되도록 한다.
- 자바스크립트 코드를 토크나이저가 어휘 분석하여 문법적 의미를 갖는 코드의 최소 단위인 토큰들로 분해하는데 이것을 토큰나이징이라 한다.
- 파서가 토큰들을 구문분석하여 AST(Abstract Syntax Tree : 추상 구문 트리)로 파싱한다.
- 바이트 코드 생성기가 AST를 바이트코드로 변환한다.
- 인터프리터에 의해 바이트코드를 실행한다.
렌더 트리 생성
렌더링 엔진은 서버로부터 응답된 HTML과 CSS를 파싱하여 각각 DOM과 CSSOM을 생성하고 렌더링을 위한 렌더 트리로 결합된다.
브라우저 화면에 렌더링 되지 않는 노드(예 : meta 태그, script 태그)와 CSS에 의해 비표시(예 : display: none;)되는 노드들은 포함되지 않는다.
레이아웃 계산
브라우저의 뷰포트(Viewport : 브라우저에서 페이지가 그려지는 영역) 내에서 렌더 트리 노드들이 가지고 있는 스타일과 속성에 따라서 브라우저 화면에 어떻게 그릴지 정확한 위치와 크기를 계산한다.
%, vw, vh 와 같은 단위들이 뷰포트 크기에 맞게 픽셀 단위로 변환된다.
페인트
브라우저 화면에 실제 노드들이 픽셀 단위로 나타나도록 렌더 트리가 페인팅 처리에 입력되도록 paint 메서드가 호출된다.
이때 처리해야 하는 스타일이 복잡할수록 소요되는 시간이 늘어나게 된다. 단색의 경우는 빠르지만 그라데이션이나 그림자 효과 등은 소요시간이 비교적 더 오래 걸린다.
리플로우와 리페인트
- 만약 자바스크립트 코드에 DOM이나 CSSOM을 변경하는 DOM API가 사용된 경우, 변경된 DOM과 CSSOM은 다시 렌더 트리로 결합되고 레이아웃 계산이나 페인트 처리를 하게 되는데, 이를 리플로우, 리페인트라 한다.
- 리플로우는 레이아웃을 다시 계산하는 것을 말하는데 색상이 아닌 즉, 노드의 추가나 삭제, 크기나 위치를 다시 계산하는 것을 말한다.
- 리페인트는 레이아웃에 영향이 없는 경우 리플로우 없이 실행될 수 있다.
- 리플로우가 일어나는 경우
- 페이지 초기 최초 렌더링
- 윈도우 리사이징 (Viewport 크기 변경 시)
- 자바스크립트에 의한 노드 추가 또는 삭제
- position, width, height, top, right, bottom, left, margin, padding, border, border-width, clear, display, float, font-family, font-size, font-weight, line-height, min-height, overflow, text-align, vertical-align, white-space ...
- 리페인트가 일어나는 경우
- 레이아웃에는 영향을 주지 않는 스타일 속성이 변경되었을 때는 리플로우 없이 리페인트만 일어난다.
- background, background-image, background-position, background-repeat, background-size, border-radius, border-style, box-shadow, color, line-style, outline, outline-color, outline-style, outline-width, text-decoration, visibility
- 리플로우, 리페인트 줄이는 방법
- top, left, width, height 보다 transform 사용
- visiblity/ display 보다 opacity 사용
- 클래스 변화에 따라 스타일을 변경할 때, 최대한 DOM 구조상 끝단에 위치한 노드에 주기
- 인라인 스타일을 최대한 배제
- 애니메이션이 들어간 엘리먼트에 position: fixed 또는 position: absolute로 지정하여 다른 요소들의 레이아웃에 영향을 끼치지 않게 한다.
- 테이블을 사용한 레이아웃 피하기 : 테이블은 모두 로드되고 계산된 후에 화면에 뿌려지므로, 테이블 레이아웃에 작은 변화가 있으면 해당 테이블 전체 노드에 대한 리플로우를 발생시킨다. 따라서 레이아웃 용도가 아닌 데이터 표시의 올바른 용도로 사용하 되 table-layout: fixed 속성을 주는 것이 디폴트 값인 auto에 비해 성능면에서 더 좋다.
- 자바스크립트를 통해 스타일 변화를 줄 때 여러줄이 아닌 한 줄로 처리하기
var toChange = document.getElementById('elem');
toChange.style.background = '#333';
toChange.style.color = '#fff';
toChange.style.border = '1px solid #ccc';
/* CSS */
#elem { border:1px solid #000; color:#000; background:#ddd; }
.highlight { border-color:#00f; color:#fff; background:#333; }
/* js */
document.getElementById('elem').className = 'highlight';
참고
'Programming > JavaScript' 카테고리의 다른 글
자바스크립트 소수점 계산 오류 (1) | 2021.03.04 |
---|