DOM과 브라우저 렌더링

Haeun·2022년 12월 28일
0
post-thumbnail

DOM (Document Object Model)이란?

웹 페이지에 대한 프로그래밍 인터페이스이다.

기본적으로 여러 프로그램들이 페이지의 콘텐츠 및 구조, 스타일을 읽고 조작할 수 있는 API를 제공한다.

원본 HTML 문서의 객체 기반 표현 방식이며, 개체 구조는 노트 트리(하나의 부모 줄기가 여러 개의 자식 나뭇가지, 나뭇잎들을 가질 수 있는 나무와 같은 구조)로 표현된다.

<!doctype html>
<html lang="en">
	<head>
		<title> My first web page </title>
	</head>
	<body>
		<h1> Hello, world! </h1>
		<p> How are you? </p>
	</body>
</html>

DOM을 이해하기 위해서는 웹 페이지의 빌드 과정을 알아야 한다.

CRP (Critical Rendering Path)

브라우저가 서버에서 페이지에 대한 HTML 응답을 받으면 화면에 표시되기 전에 많은 단계를 거쳐야 하는데, 웹 브라우저가 HTML 문서를 읽어들인 후, style을 입히고 대화형 페이지로 만들어 뷰 포트에 표시하기까지의 과정을 Critical Rendering Path, CPR 이라고 한다.

CPR의 6단계 과정

  • DOM 트리 구축
  • CSSOM 트리 구축
  • JavaScript 실행
  • 렌더 트리 구축
  • 레이아웃 생성
  • 페인팅

DOM과 HTML의 차이점

DOM은 HTML 문서로부터 생성이 되지만 항상 동일하지 않다.

  • HTML : 화면에 보이고자 하는 모양과 구조를 문서로 만든 것으로 단순 텍스트로 구성

  • DOM : HTML 문서의 내용과 구조가 객체 모델로 변화되어 다양한 프로그램에서 사용될 수 있다.


DOM은 브라우저에서 보이는 것이 아니다.

브라우저 뷰 포트에 보이는 것은 렌더 트리로 DOM과 CSSOM의 조합이다.

렌더 트리는 오직 렌더링 되는 요소만 관련이 있고 스크린에 그려지는 것으로 구성되어 있어

시각적으로 보이지 않는 요소들은 제외되기 때문에 DOM과는 다르다.

  • ex) display: none 스타일 속성을 가지고 있는 요소
    • 렌더 트리 : 포함 X
    • DOM : 포함

DOM은 개발 도구에서 보이는 것이 아니다.

개발 도구의 요소 검사기는 DOM과 가장 가까운 근사치를 제공하지만

개발 도구의 요소 검사기에는 DOM에 없는 추가적인 정보를 포함한다.

::before과 ::after 선택자를 사용하여 생성된 가상 요소는 CSSOM과 렌더 트리의 일부를 구성하지만 DOM은 오직 원본 HTML 문서로부터 빌드 되고, 요소에 적용되는 스타일을 포함하지 않기 때문에 기술적으로 DOM의 일부는 아니다.


CSS 선택기를 사용하여 DOM 요소 선택 방법

1. querySelector

  • document.querySelector : CSS 선택자에 해당하는 가장 첫번째 DOM Element를 잡아온다.
  • document.querySelectorAll : CSS 선택자에 해당하는 모든 DOM Element를 잡아온다.

2. ID, Class, Tag

  • getElementById : 주어진 문자열과 일치하는 id 속성을 가진 요소를 찾아온다.
  • getElementByClassName : 주어진 class 이름을 가진 모든 자식 요소를 배열 객체로 반환한다.
  • getElementByTagName : Element의 HTMLCollection과 주어진 태그명을 반환한다.

3. 부모, 형제, 자식

  • parentElement : 호출된 Element의 부모 요소를 반환한다.
  • nextElementSibling : 호출된 Element의 바로 뒤에 있는 요소(형제)를 반환한다.
  • previousElementSibling : 호출된 Element의 바로 앞에 있는 요소(형제)를 반환한다.
  • children : 호출된 Element의 모든 자식들을 반환한다.

DOM Elements 제어 방법

1. DOM Element 생성

var element = document.createElement('tagName');

2. Add, remove, toggle을 이용하여 class 제어하기

var addClass = element.classList.add('className');

var removeClass = element.classList.remove('className');

var removeClass = element.classList.toggle('className');

3. 속성 추가하기

setAttribute : 지정된 속성을 Element에 추가하고 지정된 값을 제공한다.

element.setAttribute('attribute', 'value');
  • attribute : 속성의 특정 이름을 설정한다.
  • value : 속성에 값을 할당한다.

4. Element 제거하기

remove() : node를 제거한다.

var el = document.querySelector('div');
el.remove();

node : 여러가지 DOM타입들이 상속하는 인터페이스
ex) Document, Element, CharacterData 등

5. Element 추가하기

appendChild() : 특정 부모 node의 자식 node 리스트 중 마지막 자식으로 붙인다.

element.appendChild(aChild);
  • element : 부모 요소
  • aCild : 지정된 부모의 밑으로 들어갈 자식 요소
var li = document.createElement('li');
var span = document.createElement('span');
li.appendChild(span);

DOM Events

addEventListener()

지정한 이벤트가 대상에 전달될 때 마다 호출할 함수를 설정한다.

target.addEventListener(type, listener);
  • type (이벤트 명) : 반응할 이벤트의 유형 및 이름 (ex : ‘mouseover’)
  • listener (이벤트 핸들러) : 지정된 타입의 이벤트가 발생했을 때 실행 될 함수



🌏 브라우저란?

MDN 에서는 브라우저를 웹 브라우저 또는 브라우저는 웹에서 페이지를 찾아서 보여주고, 사용자가 하이퍼링크 (en-US)를 통해 다른 페이지로 이동할 수 있도록 하는 프로그램 이라고 설명한다.

말하자면 웹 서버와 통신하여 인터넷 사이트 및 다양한 컨텐츠를 볼 수 있도록 지원해주는 소프트웨어 프로그램이다. 브라우저의 주요 기능은 사용자가 선택한 자원을 서버에 요청하고 브라우저에 표시하는 것이다.


브라우저의 기본 구조


  • 사용자 인터페이스
    페이지를 보여주는 창 외에 사용자가 컨트롤 할 수 있는 부분
    주소 표시줄, 이전/다음 버튼, 북마크 메뉴 등.

  • 브라우저 엔진
    사용자 인터페이스와 렌더링 엔진 사이의 동작을 제어한다.
    Data storage 컴포넌트와 통신할 수 있다. (자료 저장소로 이동하는 역할)


  • 렌더링 엔진
    사용자가 URI를 입력했을때 그에 해당하는 데이터를 파서를 통해 html, css를 파싱한 후 렌더링.
    요청한 콘텐츠를 표시. 예를 들어 HTML을 요청하면 HTML과 CSS를 파싱하여 요청받은 내용을 화면에 그려주는 일을 한다. 일반적으로 HTML+CSS를 보여주지만 플러그인의 도움으로 PDF, XML등의 문서도 나타낼 수 있다.

  • 통신
    렌더링 엔진으로부터 http요청 명령을 받아 서버에게 요청하고, 응답을 받아서 렌더링 엔진에게 반환.
    HTTP 요청과 같은 네트워크 호출에 사용된다.

  • UI 백엔드
    최종 렌더트리를 브라우저에 그리는 역할을 담당한다.
    콤보 박스와 창 같은 기본적인 장치를 그린다. 플랫폼에서 명시하지 않은 일반적인 인터페이스로서, OS의 유저 인터페이스 메서드를 사용해 창이나 콤보박스, 체크박스 등의 기본적인 위젯을 그려준다.

  • 자바스크립트 해석기(인터프리터)
    자바스크립트 코드를 해석하고 실행한다.
    Html의 태그들에 동적인 기능들을 적용하는 등의 자바스크립트를 파싱하는 역할
    크롬에서는 v8이라는 엔진을 이용한다.

  • 자료 저장소
    쿠키등의 자료들을 컴퓨터 하드디스크에 저장.
    localStorage나 Cookie와 같이 보조 기억장치에 데이터를 저장하는 자료저장소.

💻 브라우저 주소창에 www.naver.com 을 검색하면?

브라우저 검색창에 www.naver.com 을 치고 엔터! 를 누르면 이러한 일련의 과정을 거치게 된다. 렌더링 작업 전에 일어나는 과정을 간략히 살펴보자!

✔ 1. 입력한 text 정보를 확인

내가 입력한 텍스트가 검색어인지, url 주소인지 확인한다.
텍스트가 url 주소일 경우 브라우저 엔진에서 네트워크 호출을 수행한다

✔ 2. 네트워크 호출

네트워크 호출을 간단하게 설명하자면 컴퓨터 세상의 집 주소라고 할 수 있는 IP주소를 찾는 과정이다. 내 브라우저에 네이버 화면을 띄워야하는데 네이버에 대한 정보가 없으니 정보가 있는 주소에 가서 요청을 해야하는 것이다. 그 정보는? 당연히 '네이버' 라는 회사 서버에 있다. 때문에 브라우저는 네이버 서버와 통신하여 데이터를 가져와야한다.

2-1. IP 주소 찾기

📌 IP 주소 = 사이트의 주소

네이버 서버의 주소를 확인하기 위해 사용자(클라이언트)는 DNS 서버에 검색하기 전에 캐싱된 DNS 기록들을 먼저 확인한다. 만약 해당 도메인 (naver.com) 이름에 맞는 IP 주소 (125.209.222.142)가 존재하면, DNS 서버에 해당 도메인 이름에 해당하는 IP주소를 요청하지 않고 캐싱된 IP주소를 바로 반환한다.

인터넷 세상에서는 IP 주소를 기반으로 동작하지만 인간인 우리는 그것을 식별하기 어렵기 때문에 IP주소 대신 문자로 이루어진 도메인주소를 사용한다.

때문에 도메인 주소를 IP주소로 변환해주는 DNS(Domain Name Server)가 필요한 것이다. 이를 수행하는 것이 DNS 서버이다.

DNS서버는 도메인 주소에 대응하는 IP 주소를 찾아주는 역할을 수행한다.

만약 DNS 기록에 가려고 하는 URL과 일치하는 IP주소가 존재하지 않는다면 DNS 서버에 해당 사이트 IP주소를 요청한다.

2-2. HTTP 통신

이제 브라우저가 네이버의 IP주소를 알게 되었으므로 네이버 서버와 통신할 수 있게 되었다!

클라이언트의 브라우저는 네이버 서버에 데이터를 요청하는 HTTP Request를 보낸다.

request를 받은 네이버서버는 이를 바이트 형태 (0과 1로 이루어짐)로 변환하여, 클라이언트로 HTTP Response를 보낸다.



여기까지가 데이터가 불러와지는 과정이다. 이제 이 받아온 데이터를 화면에 그려야 한다!

💻 렌더링

렌더링이란?

웹 브라우저에 출력되는 단계를 Critical Rendering Path 라고 한다.

받아온 데이터의 내용대로 화면에 그리는 것을 렌더링이라고 한다.
렌더링에 필요한 것은 HTML과 CSS 문서이다.

[웹킷]

[개코]

렌더링 엔진은 요청받은 내용을 브라우저 화면에 표시해주는 역할을 한다. 브라우저마다 사용하는 렌더링 엔진이 각각 다르며 사파리는 webkit, 파이어폭스는 gecko 엔진을 사용한다. 크롬은 원래는 webkit 엔진을 사용하다가 그것을 fork하여 blink 엔진을 사용하고 있다.

웹킷과 게코가 용어를 약간 다르게 사용하고 있지만 동작 과정은 기본적으로 동일하다는 것을 위 두 그림에서 알 수 있다.

브라우저 렌더링 과정


네트워크 통신 이후 렌더링 과정은 총 4단계로 이루어져 있다.

DOM 트리 구축을 위한 HTML 파싱

트리 형태로 구조화된 데이터 표현인 DOM 트리 구축을 위해서는, HTML 문서를 받아서 파싱하고 태그(p, span) 등을 DOM 노드로 변환한다. 그 다음 CSS 파일과 함께 포함된 스타일 요소를 파싱한다. 스타일 정보와 HTML 표시 규칙들은 Render Tree라고 부르는 또 다른 트리를 생성한다.

Render Tree (렌더 트리 구축)

DOM과 CSSOM을 결합하여 화면에 그려낼 내용을 작성한다. 즉 어떤 엘리먼트에 어떤 스타일을 적용할 것인지 정해진다.

Layout (렌더 트리 배치)

각 요소의 크기와 위치를 계산한다.

Paint (렌더 트리 그리기)

  1. 요소를 실제로 픽셀로 변환하여 그린다. (Paint 단계, Rasterize)

  2. 픽셀로 그려진 여러 레이어를 합성한다. (Compositing 단계)



🖇 파싱

렌더링 과정 중 가장 중요한 것이 파싱(Parsing) 이다.

브라우저는 HTML CSS 등의 단순한 텍스트 문서를 이해할 수 없기 때문에 브라우저가 읽을 수 있도록 변환하는 과정이 필요하다. 즉, 파싱은 서버로부터 전송받은 문서의 문자열을 브라우저가 이해할 수 있는 구조로 변환하는 과정이라고 할 수 있다.

  • HTML 파싱
    렌더링 엔진이 HTML 문서를 수신받으면, HTML 파서는 이를 위에서부터 읽어 내려가며 파싱을 진행하고, 그 결과물로 DOM 트리를 생성한다.

  • CSS 파싱
    CSS 문서를 파싱하여 CSSOM 트리를 생성한다. 앞서 렌더링 엔진은 HTML문서를 한 줄 씩 파싱하면서 DOM을 생성한다고 했다. 이 과정에서 CSS 문서를 연결한 <link>, <style> 태그를 만나면 CSS문서를 파싱하여 스타일 규칙을 담고있는 CSSOM 트리를 만들기 시작하는 것이다.
    CSS는 렌더링을 할 때 반드시 필요한 리소스이기 때문에 브라우저는 빠르게 CSS를 다운로드하는 것이 좋다. 그래서 <head> 태그 안에서 정의하여 빠르게 리소스를 받을 수 있도록 하는 것이다.

  • JS 파싱
    파싱 과정에서 <script> 태그 만나면 렌더링 엔진은 파싱을 잠시 중단하고 script를 읽는다. 그리고 JS파싱이 끝나면 렌더링 엔진은 다시 HTML 문서를 파싱한다.
    <script> 태그가 <body> 태그 중간에 위치할 경우 HTML 파싱을 하는 도중에 갑자기 JS 파일을 읽게 되므로 오류 발생 위험이 있다. 그래서 우리가 <script> 태그를 <body> 태그 최하단에 쓰는거다~!

🖇 Render Tree


파싱하여 만든 DOM 트리와 CSSOM 트리를 결합하는 것을 Attachment 라고 한다.
Attachment를 통해 Render Tree를 구축한다.

렌더 트리란 실제 화면에 표현되는 노드들로만 구성된 트리를 뜻한다. 여기서 화면에 표현되는 것은 눈에 보여지는 것이 아닌 화면 상에서 공간을 차지하느냐이다.

🖇 Layout

렌더트리의 노드를 화면에 배치하는 과정을 레이아웃이라고 한다.

렌더트리 생성이 끝나면 웹페이지 화면 안에서 렌더트리에 있는 각 노드의 위치와 크기, 너비, 높이 등을 계산하고 화면에 배치하는 레이아웃 과정이 실행된다. 뷰포트 내에서 생성된 render tree의 각 노드들이 스크린 상의 어느 공간에 위치해야할지가 결정된다.

이때 모든 상대적인 값(rem, vw 등)이 절대단위인 px로 변환된다.

레이아웃은 전체의 배치과정이 필요한 경우인 글로벌 레이아웃, 일부의 배치과정만 변경하면 되는 경우인 로컬 레이아웃으로 구분할 수 있다.

처음 배치되거나 font와 같은 전역 스타일이 변경될 경우, 창이 resize되는 경우 전체적으로 layout 과정이 일어난다. (글로벌 레이아웃) 초기 배치 이후 요소의 크기나 위치가 변할 때 Layout이 다시 일어나는 것을 리플로우(reflow) 라고 한다.

로컬 레이아웃은 초기 배치 이후 일부 DOM 노드에 변경이 생기는 것처럼, 특정 부분만 재배치가 필요할 때 발생한다. 화면의 일부만 바뀌어도 전체 배치를 새로 하는 것은 너무나 비효율적이기 때문에 화면의 구조가 바뀌었을 때 일어나는 것이다.

🖇 Paint

레이아웃 과정에서 계산된 정보들을 바탕으로 각 노드들을 화면에 그려주는 과정

렌더 트리의 각 노드를 화면의 실제 픽셀로 나타낼 때 Painting메서드가 호출된다. Painting 과정 후 브라우저 화면에 UI가 나타나게 된다. 브라우저 성능 개선을 위해 각 노드를 레이어별로 나눠서 준비한다. 그리고 변경되는 요소가 있을 경우 그 레이어만 업데이트한다.

이러한 과정이 모두 끝나면

드디어 네이버 화면이 보이게 되는 것이다.
검색이 아니고서도 내가 코딩한 화면도 이런 과정을 거쳐서 보이는 것이다.
이러한 과정이 몇 초 만에 일어나다니 정말 신기할 따름



Reflow와 Repaint

위 4단계의 과정을 모두 마치면 브라우저에 화면이 그려진다.

하지만 특정 액션이나 이벤트에 따라 HTML요소의 크기나 위치 등의 레이아웃 수치가 변하면 해당 요소의 영향을 받는 자식 노드나 부모 노드들을 포함하여 Layout(Reflow)과정을 다시 수행하게 된다.

이럴 경우 각 요소들의 크기와 위치를 다시 계산하게 되는데 이 과정을 Reflow,

그리고 Reflow 된 렌더 트리를 다시 화면에 그려주는 과정Repaint라고 한다.

Reflow가 일어나는 경우

  • 페이지 초기 렌더링 시 (최초 Layout 과정)
  • 브라우저 리사이징 시 (Viewport 크기 변경)
  • 노드 추가 또는 제거
  • DOM 노드의 위치 변경
  • DOM 노드의 크기 변경 (margin, padding, border, width, height 등..)
  • 요소의 위치, 크기 변경
  • 폰트 변경과 이미지 크기 변경

Repaint
Reflow만 수행되면 실제 화면에는 반영되지 않기 때문에 다시 Painting이 일어나야 한다. 이 과정을 Repaint라고 한다.
Reflow가 발생하지 않아도 background-color 나 opacity 같이 레이아웃에 영향을 주지 않는 스타일 속성이 변했을 때는 reflow 없이 repaint만 일어난다.

Reflow와 Repaint 차이
Reflow(Layout)는 뷰포트 내에서 렌더 트리의 노드의 정확한 위치와 크기를 계산하는 과정.
Reflow가 발생하는 경우는 화면의 구조가 바뀌었을 경우.
Repaint(Redraw)는 화면에 변화가 있을 때 화면을 다시 그리는 과정.
Repaint가 발생하는 경우는 화면이 변경되는 모든 경우.


렌더링 최적화

렌더링 최적화하기 위해서는

1. Reflow 최소화하기

Reflow가 발생하면 필연적으로 Repaint가 일어나기 때문에
브라우저가 더 빠르게 렌더링하기 위해서는 Reflow 과정을 최소화 시켜야 한다.
Reflow가 발생하는 속성보다 Repaint만 발생하는 속성을 사용하는 것이 좋다.

2. CSS문서와 JS문서로 인한 비효율 줄이기

  • <head> 태그 안에 <link>, <style>과 같은 css문서 배치

  • 미디어 유형과 미디어 쿼리 이용 (해당 조건에 부합될 때만 로드되므로)

  • HTML 파싱 도중 <script> 태그를 만나면 DOM 생성을 중단하고 javascript가 실행되게 한 후 DOM 생성을 재개한다.

  • <body> 최하단에 <script> 태그 위치

  • <script> 태그에 async 속성 부여

  • <script> 태그에 defer 속성 부여

3. 영향을 주는 노드 최소화하기

JavaScript와 CSS를 조합해 애니메이션이나 레이아웃 변화가 많은 요소의 경우
position을 absolute 또는 fixed를 사용하면 영향을 받는 주변 노드들을 줄일 수 있다.

fixed와 같이 영향을 받는 노드가 전혀 없는 경우
Reflow 과정이 전혀 필요 없어지기 때문에 Repaint 연산 비용만 들게 되어 효율적이다.



💭 DOM 요약 정리

DOM은 HTML 문서에 대한 인터페이스이다.

DOM이 사용되는 곳

  • 뷰 포트에 무엇을 렌더링 할지 결정하기 위해 사용된다.
  • 페이지의 컨텐츠 및 구조, 스타일이 JavaScript 프로그램에 의해 수정되기 위해 사용된다.

DOM과 HTML의 차이점

  • DOM은 항상 유효한 HTML 형식이다.
  • DOM은 JavaScript에 의해 수정될 수 있는 동적 모델이어야 한다.
  • DOM은 가상 요소를 포함하지 않는다. (ex. ::after)
  • DOM은 보이지 않는 요소를 포함한다. (ex. display: none)
profile
하쿠키

0개의 댓글