🎉 berenickt 블로그에 온 걸 환영합니다. 🎉
Lang
JS-DOM
02-DOM의 기본개념(CSSOM, Render Tree, CRP)

1. DOM이란?

  • Document Object Model의 약자
  • JS는 HTML 조작에 특화된 언어

JS에선 <p></p> 이런 HTML을 직접 해석하고 조작할 수 없습니다.

1
<script>
2
<!-- 에러남 -->
3
;(<p></p>).innerHTML = '안녕'
4
</script>

JS는 <p> 이런건 못알아듣습니다. 그런데 어떻게 HTML 태그들을 알아보고 조작할 수 있는 것일까요? JS가 HTML 조작을 하기 위해서는, HTML을 자바스크립트가 해석할 수 있는 문법으로 변환해놓으면 됩니다. 그래서 HTML을 JS가 알아먹는 array 혹은 object 자료형에 담아버립니다.

그래서 실제로 브라우저는 HTML 페이지를 열어줄 때, HTML을 JS로 쉽게 찾고 바꾸기 위해 object와 비슷한 자료형에 담아줍니다.

예를 들어,

1
<div style="color : red">안녕하세요</div>

브라우저는 위와 같은 HTML을 발견하면, object 자료로 바꿔서 보관해둡니다. 구체적으로는 var document = { } 이런 변수를 하나 만들어서 거기 넣어줍니다.

1
var document = {
2
div1 : {
3
style : {color : 'red'}
4
innerHTML : '안녕하세요'
5
}
6
}

이제 document.div1.innerHTML = '안녕' 이렇게 자바스크립트를 짜면 HTML 조작이 가능합니다. 그래서 저렇게 object에 담아두는 것입니다. (물론 실제 DOM과 생김새는 좀 다름)

위 변수를 document object라고 부릅니다. 여기다 model이라고 붙여서 DOM (Document Object Model)이라고 합니다.


2. 브라우저는 위에서부터 읽음

브라우저는 HTML문서를 위에서 부터 차례로 읽어내려갑니다. 읽을 때 마다 HTML을 발견하면 DOM에 추가해줍니다.

1
<script>
2
document.getElementById('test').innerHTML = '안녕'
3
</script>
4
5
<p id="test">임시글자</p>

위와 같이 코드짜면 에러가 납니다. 왜냐면 브라우저는 HTML을 위에서부터 한줄한줄 읽습니다. 그런데 갑자기 JS로 <p id="test">인 요소를 DOM에서 찾고 바꾸라고 해서 에러가 날 뿐입니다. 왜냐면 아직 <p id="test">를 읽기 전이라 p태그에 대한 DOM이 아직 생성되지 않았으니까요. 이렇듯 자바스크립트는 DOM이 생성된 경우에만 HTML을 변경할 수 있습니다.


2.1 DOMContentLoaded 이벤트 리스너

“이 코드는 HTML 전부 다 읽고 실행해주세요” 라고 코드짜놓을 수 있습니다.

1
<script>
2
// "HTML을 다 읽어들였는지"를 알려주는 이벤트리스너
3
document.addEventListener('DOMContentLoaded', function () {
4
document.getElementById('test').innerHTML = '안녕'
5
})
6
</script>
7
8
<p id="test">임시글자</p>

JS를 <body>태그 끝나기 전에 전부 작성하기 때문에, JS 위치를 내가 정할 수 없을 경우에만 유용한 방법


2.2 load 이벤트 리스너

load 이벤트리스너를 사용하면 DOM 생성뿐만 아니라 이미지, css, js파일이 로드가 됐는지도 체크가능합니다.

  • 외부 자바스크립트 파일에 저걸 적어놓으면,
  • js 파일보다 이미지가 더 먼저 로드되는 경우도 있으니 이벤트 발생체크를 못할 수도 있음
1
window.addEventListener('load', function () {
2
// document 안의 이미지, js 파일 포함 전부 로드가 되었을 경우 실행할 코드
3
})

3. CSSOM

CSSOM은 CSS Object Model의 약자입니다. 브라우저에서 HTML 파일을 분석하면 HTML 요소들을 DOM으로 변환해서 이해하는데, 그러면 정의한 스타일 CSS는 어떻게 이해할까요? 브라우저에서 DOM을 만들게 되면, 정의된 CSS를 병합해서 CSSOM을 만듭니다. DOM + CSS = CSSOM

CSSOM에서는 개발자가 정의한 스타일뿐만 아니라 브라우저에서 기본적으로 설정된 모든 속성값들, 즉 cascading 룰에 따라서 합해진 모든 CSS 값들이 정의되어져 있습니다. 이를 Computed Styles라고 부르는데, 모든 것들이 이미 계산된 스타일을 말합니다.

cf. MDN CSSOM


4. Render Tree

DOM + CSSOM = Render Tree

브라우저가 HTML 파일을 읽게 되면, 제일 처음 DOM 트리가 만들어지고, 그 다음에 CSS 파일을 읽은 다음 전부 계산해서 최종적으로 확정된 CSSOM을 만들게 됩니다. 그리고 이거 다음에 DOM과 CSSOM을 합해서 즉, 최종적으로 브라우저에 표기될 요소들만 Render Tree에 선별되어서 표기가 됩니다.

Render Tree에 body태그만 있는 것을 확인할 수 있는데, 왜냐하면 DOM의 head 태그는 사용자들에게 보여지지 않기 떄문입니다.

예를 들면, span의 opacity: 0, visibility: hidden 으로 설정되었다면, 사용자 눈에는 보이지 않지만 요소는 그대로 그 자리에 있습니다. 다만 투명해질 뿐인거죠. 이런 요소들은 Render Tree에 포함됩니다.

반대로, display : none라고 되어있다면, 아예 사용자 눈에 보이지 않는 속성값들은 Render Tree에 포함되지 않습니다.

지금까지 브라우저가 아무리 간단한 HTML 페이지라도 사용자에게 보여주기까지는 많은 절차가 진행됩니다.

  1. DOM으로 변환
  2. CCSOM을 만들어 최종 스타일을 전부 계산한 다음
    • CSSOM에는 Cascading(폭포) 규칙이 존재하기 때문에 CSS를 따로 정의하지 않아도
    • 브라우저에 기본 설정된 CSS 파일 등이 전부 적용
    • 밑에 있는 자식 요소들에도 부모 요소들의 속성값들을 물려받음
  3. Render Tree를 만들어 사용자게 보여준다.

5. 성능 보장 렌더링 순서(Critical Rendering Path)

웹 페이지나 웹 어플리케이션을 브라우저가 사용자에게 보여주기까지 어떤 과정을 거치는지에 대해서 알아볼 것입니다. 이 과정을 이해해야 성능이 좋은 웹 어플리케이션을 만들 수 있습니다. 나중에 다른 JavaScript 프레임워크나 라이브러리를 쓰던, 다른 CSS 전처리기를 쓰던, 애니메이션을 CSS로 하던, 기본적인 내용이 잘 정리되어야 성능좋은 웹페이지를 만들 수 있습니다.

브라우저에서 URL을 입력하게 되면 다음와 같은 순서로 진행됩니다.

  1. 브라우저가 서버에게 HTML 파일을 요청 : Requests/Response
  2. HTML 파일을 서버에게 받아서 로딩 : Loading
  3. 로딩받은 HTML 파일을 한 줄씩 읽어서 : Scripting
    1. DOM 요소로 변환
    2. CSS 요소를 CSSOM으로 변환
  4. 변환한 CSSOM을 브라우저 window에 표기하기 위해 Rendering Tree를 준비 : Rendering
  5. 각각의 요소들이 어떤 위치에 얼마나 크게 표기될 건지 계산 : Layout
  6. 그림을 그린다. : Painting

이것을 조금 더 어떤 일을 하냐를 카테고리로 나눠서 생각해보면, 크게 2가지 파트로 나눌 수 있습니다.

  • Construction 파트
    • HTML 페이지에서 브라우저가 이해할 수 있도록 브라우저만의 언어로 바꾸는 부분
    • DOM요소로 변환하고, CSSOM을 만들고, Render Tree를 최종적으로 만드는 것까지를 의미
  • Operation 파트
    • 브라우저가 이해할 수 있는 Rendering Tree를 이용해서 구조를 작성하고, 어디에다 배치할 건지 계산한 다음에 실제 브라우저에 그림을 그려주는(redering) 부분
    • layout, paint, composition을 통해 최종적으로 사용자에게 웹페이지 내용이 보여지기까지를 의미

5.1 layout

Layout이란 무엇일까요? Render Tree에는 DOM 요소뿐만 아니라 최종적으로 계산된 CSS 스타일이 포함되어 있습니다. 이제 이 정보를 기반으로 window 위에서 해당 요소가 어느 위치에 어느 크기로 배치할 지 등의 레이아웃을 구상하게 됩니다. X와 Y, 너비과 높이 등의 크기들을 계산하겠죠. 이제 이런 레이아웃을 맞춰야지 정확하게 어디에다가 얼마만큼 크게 그림을 그려야 될지 계산이 됩니다.


5.2 paint

그 다음 paint 과정이 일어나는데, 계산한 요소들을 바로 브라우저에 그림을 그리는 것이 아니라 이 요소들을 어떻게 배치했느냐에 따라서 paint 부분에서는 각각 부분을 조금씩 잘게 나누어서 이미지를 준비해 놓습니다. 각각의 요소들의 이미지를 비트맵이라고 하는데, 즉 컴퓨터가 이해할 수 있는 이미지를 비트맵 데이터 형태로 변환하게 됩니다. CSS에서 Z-index를 쓰게 되면 paint 부분이 요소들을 묶어서 레이어 단계를 만들어 레이어 별로 paint를 준비해놓습니다.

꼭 Z-index 별로 아니라 다양한 속성값에 따라 브라우저 엔진마다 성능 개선을 위해 레이어를 만듭니다.

그러면 왜 브라우저는 한 번에 그리지 않고 레이어 기능을 이용해 각 레이어 별로 준비할까요? 이것은 브라우저가 자체적으로 성능 개선을 위해 준비를 해놓는 것입니다. 만약 레이어 기능을 이용하지 않고 개발자가 요소의 위치를 움직이거나 투명도를 변환하다면, 브라우저는 매번 그림을 처음부터 다시 그려야 합니다. 하지만 이렇게 레이어 단위로 그리게 되면, 해당 요소가 변화되면 해당 요소의 레이어만 다시 그리면 됩니다.

이는 PhotoShop의 레이어 기능과 똑같은 원리로 동작하는 것입니다. 레이어를 만들고 그 위에 또 다른 레이어를 만들어 겹치면서 그림을 그리는 것이죠. 이렇게 만들다 무언가 잘못되었다면 제일 위에 있는 레이어만 지우면 되겠죠? 그리고 다시 새로운 레이어를 만들어서 그림을 그리면 됩니다. 이런 식으로 브라우저도 성능 개선을 위해 레이어 기능을 사용합니다.

그래서 CSS에 willchange라는 속성값이 있는데, 이는 브라우저에게 opacity(투명도)가 변화될지도 모른다고 애기를 해서, 새 레이어에다 추가를 해놓으라는 의미입니다. 그래서 willchange라는 속성값을 너무 많이 쓰지 마라고도 말합니다. 왜냐하면 불필요하게 너무 많이 쓰면 브라우저가 쓸데없이 불필요하게 레이어를 너무 많이 만들기 때문입니다. 각각 요소마다 새로운 레이어를 만들게 되면 너무 많은 레이어가 존재해도 성능이 나빠질 수 있습니다.


5.3 composition

미리 준비한 레이어를 순서대로 차곡차곡 브라우저 위에다가 표기하면 됩니다. z-index가 제일 높은 레이어를 제일 먼저하는 등 composition(구성 요소)를 함께 모아서 표기하게 됩니다.


5.4 Critical Rendering Path 성능

HTML 페이지에서 브라우저가 표기할 수 있는 단계까지를 Critical Rendering Path라고 부릅니다.

5.4.1 Construction 파트

DOM(HTML)에서 어떻게 Render Tree를 빠르게 만들 수 있을까요? 당연히 DOM 요소가 작으면 작을 수록, CSS 규칙이 작으면 작을 수록 Tree가 작아지기 때문에 빠르게 만들 수 있겠죠? 그래서 불필요한 태그를 쓰거나, 불필요하게 div태그를 남용한다든지, 쓸데없이 wrapping 클래스나 wrapping 요소를 만든다든지 이런 것들을 자제해야 합니다. 그래서 요소들을 최대한 작게 만드는 것이 중요합니다.

5.4.2 Operation 파트

Operation Time에는 처음에 사용자에게 표기하는 것도 중요하지만 나중에 사용자가 클릭을 통해 요소를 움직이거나 애니메이션을 쓸 떄, paint가 자주 일어나지 않도록 만드는 것이 중요하겠죠? 예를 들어, 요소의 위치를 옮기는데 무언가 다시 그림을 그려야 한다면 paint가 다시 발생하기 때문에 성능에 좋지 않겠죠. 최악의 경우는 layout을 수정해서 다른 요소들의 position도 바뀌는 경우 다시 처음부터 그림을 그려야 하기 때문에 성능에 가장 좋지 않습니다. 그래서 우리가 JavaScript나 CSS로 DOM 요소를 조작할 때, composition만 일어나면 성능이 가장 좋습니다. point가 디시 일어난다면 나쁘지도 썩 좋지도 않겠지만, layout이 다시 일어난다면 최악의 경우가 됩니다.