브라우저마다 사용하는 렌더링 엔진들이 다르다. 렌더링 엔진이 브라우저마다 다르기 때문에, 같은 소스가 브라우저마다 다르게 그려지는 크로스 브라우징 이슈가 발생.(자바스크립트 엔진이 달라 발생하기도 한다.)
크롬 브라우저(정확히는 크로미움)는 사파리 브라우저에서 사용하는 Webkit을 사용하다가 버전 28 이후 Webkit 소스를 Fork 하여 Blink 엔진을 만들어 사용.
참고: 크로미움이란?
크롬은 크로미움 기반으로 만들어진 브라우저. 크로미움은 오픈 소스 웹 브라우저입니다. https://chromium.woolyss.com/download/ko/에서 다운로드해 브라우저로 사용 가능하다.
크로미움은 V8이라는 자바스크립트 엔진과 Blink라는 렌더링 엔진을 사용하는 브라우저이다. 크롬이 크로미움 기반으로 만들어졌다는 것은 오픈 소스인 크로미움 브라우저 코드 위에 살을 덧붙여 개발되었다는 의미이다.
브라우저의 역할은 사용자의 요청을 서버에 전달하고 그 결과를 화면에 나타내는 과정인데 렌더링은 렌더링 엔진은 HTML 문서를 파싱 하여 DOM 트리를 만들고, CSS 문서를 파싱 하여 CSSOM 트리를 만든다. DOM과 CSSOM을 이용하여 렌더 트리를 만든다.
렌더 트리 생성이 끝나면 Layout(Reflow라고도 합니다)이 시작된다. 이 과정은 각 노드가 화면의 정확한 위치에 표시하기 위해 위치와 크기를 계산하는 과정을 말한다. 마지막으로 계산된 위치과 크기 등의 스타일들이 실제 픽셀로 표현하는 과정이 시작된다. 이 과정을 Paint(Rasterizing)라고 한다.
이러한 과정을 통해 브라우저가 서버에 요청한 내용의 노드들을 픽셀화 시키는 것을 브라우저 렌더링 이라고 한다.
*CSSOM (CSS Object Model): CSS 개체 모델은 자바 스크립트에서 CSS의 조작을 가능하게하는 API의 집합.
Request란 클라이언트(브라우저)가 서버에게 요청하는 사항이 정리된 객체.
Response란 앞서 Request에서 받은 요청을 처리하고 그에 해당하는 데이터를 담은 객체.
위의 두가지요청이 끝나고 나면 브라우저는 화면을 표시하기위한 정보(HTML, CSS)를 받은 상태이다.
HTML은 DOM(Document Object Model)로 변환되며 CSS는 CSSOM(CSS Object Model) 으로 변환.
이 둘은 바이트 > 문자 > 토큰 > 노드 > 객채 모델 순으로 해석.
앞서 1번과 2번을 통해서 받은 HTML파일은 바이트(Bytes) 상태이다.
이 바이트 상태의 정보를 다음과 같이 처리한다.
<html>, <body>
등, 꺽쇠괄호로 묶인 문자열) 로 변환합니다.돔 조작(DOM Manipulation)은 웹 페이지를 수정하기 위해 매우 유용하지만, 문제점도 존재한다. 만약 자바스크립트를 사용해서 <div>
태그의 색상을 업데이트한다고 하면 해당 DOM node
객체에 접근하고 색상 속성을 업데이트하면 된다. 이때는 트리의 나머지 노드에 영향을 미치지 않는다.
그러나 트리에서 하나의 노드를 추가하거나 제거한다면, 전체 트리를 다시 정렬해야 할 수도 있다. 이것은 비용이 많이 드는 작업이며, 브라우저에서는 시간과 브라우저 리소스가 필요하다. 예를 들어 DOM
에 5가지 리스트(<li></li>
)가 추가된다고 하면, 하나의 리스트마다 새 노드가 DOM
에 추가되어 트리가 매번 업데이트된다. 총 5개의 업데이트가 추가되는 것이다.
같은 예로 하나의 노드 추가나 삭제를 하여 웹 페이지 전체의 레이아웃에 영향을 받는 경우, 웹페이지의 일부 또는 전체가 다시 렌더링 될 수 있다. 이런 경우를 Reflow라고 한다. 다시 말하자면 대화식 사이트(interactive site)에서 업데이트한 후에 브라우저가 웹 페이지의 일부 또는 전부를 다시 처리하고 그려야 할 때를 의미한다.
과도한 Reflow를 피하기 위해서는 Dom
을 너무 많이 변경하면 안되며, 브라우저에 따라 다른 요소도 브라우저에 영향을 줄 수 있다. 하지만 대부분의 Javascript 프레임 워크가 DOM을 필요한 것보다 훨씬 많이 업데이트한다. 이러한 현상은 곧 속도 저하로 이어진다.
DOM
조작은 현대적인 대화식 웹(interactive site)의 핵심이다. 10개의 항목이 포함된 목록이 있는데 하나의 항목이 수정되었다면, javascript 프레임 워크는 대부분 전체 목록을 리렌더링한다. 물론 이렇게 작은 경우라면 크게 문제가 될 일이 없겠지만, 일반적인 웹사이트에서는 많은 양의 DOM을 조작할 수 있으며 비효율적인 업데이트가 발생한다.
실제 DOM
의 변경사항에 대해 DOM
에서 수행해야 할 모든 변경 사항을 가상돔(virtual DOM)에서 수행한 다음 실제 DOM
에 전달함으로써 위에서 언급한 계산 단계가 줄어든다. 여러 번의 변경사항이 있더라도 모든 변경 사항을 하나로 그룹화하여 한번만 수행한다.
하지만, 잘 생각해보면 가상돔 없이도 해결할 수 있는 문제다. 왜냐하면 결국 가상돔도 "Rendering"하기 위해서 고유 DOM API인 "document.createDocumentFragment()"를 사용하기 때문이다. DOM
의 모든 수정 사항을 직접 그룹화 한다음에 DOM
에 넘겨도 되는 것이다.
그렇다면 가상돔은 무엇을 해결하는 것일까? DOM
관리를 자동화하고 추상화하여 직접 할 필요가 없게 해주는 것이다. 또한 전체 DOM Tree
를 reload하지 않기 위해 변경한 부분과 변경되지 않는 부분을 직접 할 때는 추적해야 하나 이 또한 가상돔이 자동화해주는 것이다.
마지막으로 DOM
조작 자체를 포기함으로써 DOM
을 수정하는 모든 부분 간의 동기화를 피할 수 있다.
여기서 다시 한번 재고해봐야 하는 점은 나는 분명히 위 글에서 "가상돔이 더 빠르다"
라는 말을 하지 않았다는 점이다. 가상돔은 개발자가 작업을 보다 쉽게 할 수 있도록 도와주는 것이지, 가상돔에서 더 빠르게 접근할 수 있는 무언가를 제공해 주는 게 아니다. 이 부분은 React
의 핵심 개발자들도 vanilla js
가 가상돔에서 작업하는 것보다 항상 더 빠르다고 말하는 부분과 일맥상통하는 것이다. 가상돔은 목적을 위한 수단일 뿐이다라는 점만 기억하자!
가상돔은 html 객체에 기반하여 자바스크립트 객체로 표현할 수 있다.
const vdom = {
tagName: "html",
children: [
{ tagName: "head" },
{
tagName: "body",
children: [
{
tagName: "div",
attributes: { class: "img" },
children: [
{
tagName: "div",
attributes: { class: "name" },
textContent: "name",
}, // end div
],
}, // end div
],
}, // end body
],
}; // end html
여기서 좋은 점은 저렇게 하나의 객체가 아닌 내가 작업하는 곳만으로 작게 쪼개서 작업 할 수 있다.
const div = {
tagName: "div",
attributes: { class: "img" },
children: [
{
tagName: "div",
attributes: { class: "name" },
textContent: "name",
}, // end div
],
}, // end div
가상돔은 쉽게 말해
DOM
의 자바스크립트 객체로서의 표현이며, 작은 단위로 쪼개서 필요한 만큼 자주 수정할 수 있다. 그리고 변경되는 부분만 변경되게 하는 것은 직접DOM API
를 호출해도 되지만,React
같은 편리한 프레임워크를 이용해서 간단하게 해결할 수 있다.
react
애플리케이션을 렌더링할 때, 앱의 노드 트리가 메모리에 저장된다. 그 다음 트리는 다시 렌더링 환경으로 플러시(flush)된다. 그리고 앱이 업데이트되면(ex- setState
) 새 Tree가 생성되고 이전 트리와 비교하여 랜더링 된 앱을 업데이트하는데 필요한 작업을 계산(=비교, diffing)하고, 실제 DOM
은 변경된 내용만 업데이트 한다.
diffing
- 사전 업데이트 된virtual DOM
과 업데이트 된virtual DOM
을 비교하는 과정을diffing
이라고 한다,React
는 그래서 매번 두 개의 가상돔을 유지/관리한다.)
변경 사항이 생기면 ReactDOM.render()
가 호출 된다. 해당 과정은 변경점을 찾는 작업과, 변경점을 실제 UI 적용하는 과정으로 나누어진다.
변경점을 찾는 작업
React는 Virtual DOM을 통하여 브라우저 DOM에 전달하기 전에 Reconciliation라는 비교 과정을 선행하기 때문에 UI에 대한 제어를 최소화 시킨다. (이는 React-Native에도 동일하게 작동한다.)
Render가 진행 될 때마다, 각 Element들은 key를 부여 받고, 해당 key를 통해 같은 Element라고 인식 되고 비교 된다.(물론, 계층이 달라지게 될 경우는 제외된다. 또, React diff 알고리즘은 필터 관련 기능에 취약하다고 한다. 많은 list를 필터하는 경우, 이것을 기억하고 해결책을 찾아보기로 기약하자.)
브라우저는 DOM을 생성하는 동안 외부 CSS를 참조하는 <link>
태그를 만나게 되면 브라우저에 리소스를 요청합니다. CSS의 원시 바이트(raw bytes)가 문자열로 변환된 후 차례로 토큰과 노드로 변환되고 마지막으로 CSSOM(CSS Object Model)이라는 트리 구조를 만듭니다.
페이지에 CSS가 참조 될경우 DOM을 생성하는 동안 외부 스타일시트를 받아온다.
수신된 CSS는 위의 HTML파일과 같은 프로세스를 진행한다.
CSSOM이 트리구조를 가지는 이유는 HTML에서 나온 객체의 최종 스타일을 계산할때 상속되는 값도 같이 계산해야 하기 때문이다.
자바스크립트는 파서 차단 리소스(parser blocking resource)이다. 브라우저는 문서를 파싱 하다가 자바스크립트를 만나면 진행하던 파싱을 중지하고 자바스크립트 엔진에게 권한을 넘겨 자바스크립트를 파싱하고 실행한다.
자바스크립트가 실행되는 동안 문서의 파싱은 중단된다. 자바스크립트는 파싱을 중단시키기 때문에, 보통 자바스크립트를 <head>
태그가 아닌 <body>
태그가 닫히기 바로 전에 사용되도록 하는 것이 좋다.
<script>
태그에 defer
속성을 주면, 문서 파싱은 중단되지 않고 문서 파싱이 완료된 이후에 자바스크립트가 실행된다. HTML5에서 스크립트를 비동기(async)로 처리하는 속성이 추가되었다.
CSS는 렌더링 차단 리소스(render blocking resource)이다. CSS는 렌더링을 할 때 반드시 필요한 리소스이기 때문에 브라우저는 빠르게 CSS를 다운로드하는 것이 좋다. <head>
태그 안에서 정의하여 빠르게 리소스를 받을 수 있도록 해야한다.
CSS
는 DOM
트리를 변경하지 않기 때문에 문서 파싱을 기다리거나 중단할 이유가 없다. 그러나 자바스크립트에서 스타일 정보를 요청하는 경우, CSS
가 파싱 되지 않은 상태라면 스크립트 에러가 발생할 수 있다.
이런 문제를 해결하기 위해 파이어폭스는 로드 중이거나 파싱 중인 CSS가 있는 경우 모든 자바스크립트 실행을 중지한다. 반면 웹킷은 로드되지 않은 CSS 가운데 문제가 될 만한 속성이 있을 때에만 자바스크립트를 중단한다.
CSSOM 및 DOM 트리는 결합하여 렌더링 트리를 생성.
이 렌더링 트리는 표시되는 각 요소의 레이아웃을 계산하는데 사용되고 픽셀을 화면에 렌더링하는 페인트 프로세스에 대한 입력으로 처리.
HTML과 CSS 는 각기 개별적인 DOM과 CSSOM으로 나뉘어 있다.
이것을 합쳐서 화면에 표시하는 과정은 다음과 같다.
DOM 트리와 렌더 트리의 관계
화면에 표시되지 않는 노드들은 렌더 트리에 포함되지 않는다. 예를 들어,
<head>
태그와 같은 비시각적 DOM 노드는 렌더 트리에 추가되지 않는다.
뿐만 아니라CSS
로 인해display
속성에none
값이 할당된 노드들을 렌더 트리에 추가되지 않는다. 하지만,visibility:hidden
은 렌더 트리에 포함된다.visibility
속성에hidden
값이 할당된 노드는 화면에 공간을 차지하기 때문에 렌더 트리에 포함된다.
렌더 트리가 생성되고, 기기의 뷰포트 내에서 렌더 트리의 노드가 정확한 위치와 크기를 계산하는 과정을 Layout(혹은 Reflow)라고 한다. 모든 상대적인 측정값은 화면에서 절대적인 픽셀로 변환된다. 즉 CSS에 상대적인 값인 %로 할당된 값들은 절대적인 값은 px 단위로 변환 된다.
렌더 트리의 각 노드를 화면의 실제 픽셀로 나타내는 과정을 Painting(혹은 rasterizing)라고 한다. Painting 과정 후 브라우저 화면에 UI가 나타난다.
다음의 글을 참조하였습니다.
- https://dico.me/front-end/articles/149#:~:text=Render%2DTree,-CSSOM%20%EB%B0%8F%20DOM&text=%EC%9D%B4%20%EB%A0%8C%EB%8D%94%EB%A7%81%20%ED%8A%B8%EB%A6%AC%EB%8A%94%20%ED%91%9C%EC%8B%9C%EB%90%98%EB%8A%94,%EA%B3%BC%20CSSOM%EC%9C%BC%EB%A1%9C%20%EB%82%98%EB%89%98%EC%96%B4%20%EC%9E%88%EC%8A%B5%EB%8B%88%EB%8B%A4.
- https://beomy.github.io/tech/browser/browser-rendering/
- https://code-masterjung.tistory.com/33
- https://ryublock.tistory.com/41