렌더링 엔진
은 주된 모든 웹 브라우저의 핵심이 되는 소프트웨어 구성 요소입니다. 이의 주된 역할은 HTML 문서와 기타 자원의 웹 페이지를 사용자의 장치에 상호작용적인 시각 표현으로 변환
시키는 것입니다. 또한 렌더링 엔진은 문서들 간의 보안 정책을 강제하며 페이지 스크립트에 노출되는 문서 객체 모델(DOM) 자료 구조를 구현하고, 하이퍼링크와 웹 폼을 관리합니다.
그러나 자바스크립트 코드
를 실행하는 일은 별개의 문제인데, 주된 모든 웹 브라우저는 이를 위한 전용 엔진을 사용하기 때문입니다. 자바스크립트 언어는 원래 브라우저에 사용하기 위해 개발되었으나 현재는 그 외의 장소에서도 사용되므로 자바스크립트 엔진의 구현체는 렌더링 엔진과는 분리
되고 있습니다. 웹 브라우저에서 2개의 엔진은 공유되는 DOM 자료 구조를 통해 조화롭게 동작합니다. 이는 동작 과정에서 더욱 자세히 다룰 것입니다.
이와 같은 엔진에는 모질라 재단에서 만든 게코(Gecko)
, 사파리 등인 탑재한 웹킷(Webkit)
, 웹킷에서 파생되어 크롬, 오페라 등이 탑재한 블링크(Blink)
등이 있습니다.
위 과정대로 브라우저가 하나의 화면을 그려내는 과정을 중요 렌더링 경로(Critical Rendering Path)
라고 부릅니다. 우리가 일상적으로 접하는 주소창에 url을 입력하면 브라우저는 해당 서버에 요청을 보내게 됩니다. 서버에서는 응답으로 HTML 데이터
를 보내주는데, 이 HTML 데이터
를 실제 우리가 보는 화면으로 그리기까지 브라우저는 다음 단계를 거쳐 작업을 진행합니다. 이 과정의 각 단계가 최대한 효율적으로 이루어지도록 만드는 것
을 보통 최적화
라고 부릅니다.
각각의 단계를 짧게 설명하자면, 렌더링 엔진은 HTML 문서를 파싱
하고 콘텐츠 트리 내부에서 태그를 DOM(Document Object Model) 노드로 변환
합니다. 그 다음 외부 CSS 파일과 함께 포함된 스타일 요소도 파싱
합니다. 스타일 정보와 HTML 표시 규칙은 렌더 트리
라고 부르는 또 다른 트리를 생성합니다. 이 렌더 트리
는 색상 또는 면적과 같은 시각적 속성이 있는 사각형
을 포함하고 있는데 정해진 순서대로 화면에 표시
됩니다.
렌더 트리 생성이 끝나면 배치
가 시작되는데 이것은 각 노드가 화면의 정확한 위치에 표시
되는 것을 의미합니다. 다음은 UI 백엔드에서 렌더 트리의 각 노드를 가로지르며 형상을 만들어 내는 그리기
과정입니다.
일련의 과정들이 점진적으로 진행된다는 것을 아는 것이 중요합니다. 렌더링 엔진은 좀 더 나은 사용자 경험을 위해 가능하면 빠르게 내용을 표시합니다. 그렇기 때문에 모든 HTML을 파싱할 때까지 기다리지 않고 배치와 그리기 과정을 시작
합니다. 그리고 네트워크로부터 나머지 내용이 전송되기를 기다리는 동시에 받은 내용의 일부를 먼저 화면에 표시
할 수가 있습니다.
<html>
<head>
<meta charset="utf-8">
<link href="./style.css" rel="stylesheet">
</head>
<body>
<p>Hello, <span>web performance</span> students</p>
<div>
<img src="./image.png">
</div>
</body>
</html>
이제부터 위 코드를 예로 들어 각 단계를 더 세부적으로 살펴보겠습니다.
먼저 웹 서버에 요청을 보내면 위 코드와 같은 HTML 문서
를 응답으로 받게 됩니다. 이 때 브라우저가 받은 코드를 이해하기 위해서는 해석가능한 구조로 변환하는 과정이 필요한데, 이를 파싱(parsing)
이라고 합니다. 파싱 결과는 보통 문서 구조를 나타내는 트리(tree)
형태를 나타냅니다. 이 처럼 브라우저는 서버의 응답을 받은 후 제일 먼저 HTML 데이터를 파싱
합니다.
이 때 주의해야 할 상황이 있는데, 만약 미디어 파일
을 만나면 추가로 서버에 요청을 보내서 받아옵니다. 또한 자바스크립트 파일
을 만난다면 파싱이 중단되고, 해당 파일을 받아와서 실행한 후에 파싱이 다시 시작됩니다.
브라우저는 읽어들인 HTML 바이트 데이터를, 해당 파일에 지정된 인코딩(위 코드에서는 UTF-8)에 따라 문자열로 바꾸게 됩니다. 바꾼 문자열을 다시 읽어서, HTML표준에 따라 문자열을 토큰으로 변환
합니다. 위 이미지에서와 같이 이 과정에서 <html> 은 StartTag: html 로, </html> 은 EndTag: html 로 변환됩니다.
이렇게 만들어진 토큰들을 다시 노드로 바꾸는 과정
을 거칩니다. StartTag: html 이 들어왔으면 html노드를 만들고 EndTag:html 을 만나기 전까지 들어오는 토큰들은 html노드의 자식 노드로 넣는 식으로 변환이 이루어지기 때문에, 과정이 끝나면 Tree모양의 DOM
이 완성되게 됩니다.
여기서 DOM
이란 문서 객체 모델(Document Object Model)
의 준말입니다. 이것은 HTML 문서의 객체 표현이고 외부를 향하는 자바스크립트와 같은 HTML 요소의 연결 지점이며 마크업과 1:1의 관계를 맺습니다. 그리고 각각의 DOM 노드들을 알맞게 연결하면 위 이미지의 가장 아래부분과 같은 Tree 구조
가 됩니다.
HTML을 파싱하다가 CSS링크를 만나면, CSS파일
을 요청해서 받아오게 됩니다.
받아온 CSS파일은 HTML을 파싱한 것과 유사한 과정을 거쳐서 역시 Tree형태의 CSSOM
으로 만들어집니다. CSS 파싱은 CSS 특성상 자식 노드들이 부모 노드의 특성을 계속해서 이어받는 종속(Cascading) 규칙
을 제외하고는 HTML파싱과 동일하게 이루어집니다.
이렇게 CSSOM을 구성하는 것이 끝나야, 비로소 이후의 Rendering 과정을 시작할 수 있습니다. 이론적으로 CSS는 DOM 트리를 변경하지 않기 때문에 문서 파싱을 기다리거나 중단할 이유는 없습니다. 하지만 스크립트가 문서를 파싱하는 동안 스타일 정보를 요청하는 경우
라면 문제가 될 수 있습니다. CSS가 파싱되지 않은 상태라면 스크립트는 잘못된 결과를 내놓기 때문에 많은 문제를 일으킵니다. 그래서 CSS는 rendering의 방해하는 요소
라고 하기도 합니다. 이에 대해서는 다음 작성글에서 렌더링 최적화와 관련하여 언급하겠습니다.
CSSOM까지 만들었으면, 엔진은 DOM과 CSSOM를 합쳐서 Render Tree
를 만듭니다. 표시해야 할 순서
와 문서의 시각적인 구성 요소
로써 올바른 내용을 그려낼 수 있도록 하기 위함입니다. 여기서 중요한 점은 Render Tree는 DOM Tree에 있는 것들 중에서 화면에 실제로 '보이는' 노드들만
으로 이루어집니다. 만약 CSS에서 display: none
으로 설정하였다면, 그 노드(와 그 자식 노드 전부)는 Render Tree에 추가되지 않는 것이죠(반면에 visibility 속성에 "hidden" 값이 할당된 요소는 트리에 나타납니다). 마찬가지로 화면에 보이지 않는 <head>
태그 안의 내용들도 Render Tree에는 추가되지 않습니다.
그래서 위 이미지의 Render tree에는 <head>
태그와, display속성이 none
인 <p> 태그 하위의 <span> 태그가 사라진 것을 확인할 수 있습니다.
Render Tree가 다 만들어지면, 각각의 노드들이 화면의 어디에 위치할 지
를 계산하는 Layout
과정을 거칩니다. CSSOM에서 가져온 스타일 정보들로 이미 각 노드들의 이미지를 알고 있지만, 뷰포트를 기준으로 실제로 배치하려면 어디에 가야하는 지 계산을 해야하는 거죠. 여기에서 CSS box model
이 쓰이며, position(relative, absolute, fixed..), width, height 등과 같이 위치에 관련된 스타일 시트
들이 계산됩니다.
여기서 만약 브라우저를 사이즈를 변경할 경우 변함이 없는 노드들은 그대로인 상태에서, %, em, rem, vh, vw 등과 같은 상대 단위
로 되어있는데 노드들만 layout단계를 다시 거치게 됩니다.
이렇게 화면에 보이는 요소 각각이 어디에 어떻게 위치할 지를 정해주는 과정을 Webkit에서는 layout
으로, Gecko에서는 reflow
로 부르고 있습니다.
그리고 드디어 Render Tree의 각 노드들을 실제로 화면에 그리게 됩니다. visibility, outline, background-color같이 정말로 눈에 보이는 픽셀들이 여기에서 그려집니다.
이번 글에서는 브라우저의 엔진이 어떻게 화면을 렌더링하는지에 대해 알아보았습니다. 이제 저번 글에서 봤던 "브라우저가 화면을 그리는 원리"
에 대해서 간략히 답할 수 있겠네요. 하지만 아직 "브라우저의 렌더링을 최적화 하는 법"
은 알지 못했습니다!
다음 포스팅에서는 오늘 살펴봤던 주요 렌더링 경로(Critical Rendering Path)
에 따라 어떻게하면 브라우저의 렌더링을 최적화
할 수 있는지 알아보도록 하겠습니다.