주소창에 www.github.com
입력
통신을 위해 DNS에서 해당 도메인(www.github.com)을 가진 IP를 가져온다.
해당 IP에 페이지를 요청하고 리소스 응답을 받는다.
응답 받은 리소스들로 렌더링을 진행한다.
각 단계별로 중요한 부분은 자세히 훑어보자.
Domain Name Server의 약자로, 'Domain Name: IP' 의 쌍으로 이루어진 서버이다.
(IP를 위한 전화번호부라고 이해하면 쉽다!)
그럼 각 클라이언트 마다 DNS가 존재하는가?
- DNS는 주로 인터넷 서비스 제공업체(ISP), 케이블 인터넷 공급업체가 가진다.
HTML, CSS, JS, 이미지 파일 등 화면 렌더링에 필요한 리소스 파일을 전달받는다.
화면을 그리는 가장 중요한 과정이다. 세세하게 분석해보자.
여기서 파싱 이란 브라우저가 이해할 수 있는 자료구조 형태로 변환하는 과정을 의미한다.
HTML 파싱
HTML 태그를 파싱하여 DOM 트리를 생성한다.
바이트 > 문자 > 토큰 > 노드 > DOM
의 순서를 가진다.
CSS 파싱
html파일에서 <link>
태그나 <style>
태그를 만나면 DOM 생성 정지 후 CSS 파싱을 시작하고 CSSOM(CSS Object Model)을 생성한다.
HTML과 동일한 파싱 과정을 거친다.
렌더 트리 생성
HTML 파싱 후 생성된 DOM 트리와 CSS 파싱 후 생성된 CSSOM을 결합'(Attachment)하여 렌더 트리를 생성한다.
렌더 트리는 렌더링을 위한 트리 형태의 자료구조이다.
브라우저 화면에 렌더링되는 노드로만 구성된다.
(meta태그, script태그, css 속성{display: none}
을 가진 노드들은 포함되지 않음)
레이아웃(Reflow)
웹 화면 내에서 각 노드의 위치, 크기, 너비, 높이 등을 계산하고 화면에 배치하는 과정이다.
(스크린 상 어느 공간에 위치할 지 결정)
상대적인 값(rem, vw, % 등)은 절대단위인 px로 변환된다.
페인트(Rasterizing)
배치 된 노드들을 실제 픽셀로 나타내는 과정이다.
렌더링 과정을 최적화하면 속도를 높여 사용성을 개선할 수 있다!
JS는 파서 차단 리소스이기 때문에 파싱 도중 JS를 만나면 진행중인 파싱은 중지되고, JS엔진에게 권한을 넘겨 JS를 우선 파싱하고 실행한다.
즉, 모든 문서 파싱이 중단되기 때문에 <head>
태그가 아닌 <body>
태그가 닫히기 바로 전에 사용하는 것이 좋다.
<script>
태그에서 defer와 async 속성을 추가하면 문서 파싱이 완료된 이후 실행된다고 한다.
CSS는 렌더링 차단 리소스이다. 렌더링 시 반드시 필요한 리소스이기 때문에 빠르게 다운로드 하는 것이 좋다.
즉, <head>
태그 안에서 정의하여 빠르게 다운로드 하는 것이 중요하다.
DOM 내부/API, 스타일 변경 시 렌더링 과정을 반복한다.
리렌더링 과정에서 Reflow와 Repaint가 발생한다.
Reflow와 Repaint 과정을 줄이면 성능 향상에 도움이 된다!
DOM 요소의 속성이 변경될 때, 브라우저 사이즈 변경 시, 스타일 시트 로딩 시 발생하는 변화를 다시 계산하는 작업
주변의 모든 요소들이 영향을 받는다.(DOM 트리 전체에 해당)
=> 비용이 많이 드는 작업(해당 속성 참고)
Reflow가 호출되면 Repaint는 자동으로 호출된다.
변경된 요소를 실제로 화면에 그려주는 작업이다.
Reflow 발생 시 필연적으로 발생하지만 기하학적인 변화가 아니기 때문에 상대적으로 가벼운 작업니다.
리플로우가 최소한으로 발생되게 코드를 작성해야 한다!
// Good - Reflow 1번 발생
const h1 = div1.clientHeight
const h2 = div1.clientHeight
h1.style.height = `${h1 + 10}px`
h2.style.height = `${h2 + 10}px`
// Bad - Reflow 2번 발생
const h1 = div1.clientHeight // 최신 값에 영향을 미치기 때문에 큐에 쌓인 작업을 모두 비우고 진행
h1.style.height = `${h1 + 10}px`
const h2 = div1.clientHeight
h2.style.height = `${h2 + 10}px`
// Good
let { left } = div1.getBoundingClientRect()
for (let i=1; i<10; i++) {
div.style.left = `${left + i}px`
left += i
}
// Bad
for (let i=1; i<10; i++) {
div.style.left = `${div1.getBoundingClientRect().left + i}px`
left += i
}
// Good
div.className = "my-div";
div.style.cssText = "width: 100px; height: 100px; ...";
// Bad
div.style.width = "100px"; // reflow, repaint
div.style.height = "100px"; // reflow, repaint
div.style.padding = "5px"; // reflow, repaint
div.style.border = "5px solid blue"; // reflow, repaint
div.style.backgroundColor = "black"; // repaint
div.style.color = "white"; // repaint
{ display: none }
속성은 렌더 트리에서 제외되기 때문에 기하학적 변화가 일어나도 Reflow나 Repaint는 발생하지 않는다.div.style.display = 'none'
... div 스타일 속성 수정...
div.style.display = 'block'
참고
https://www.freecodecamp.org/news/web-page-rendering-on-the-browser-different-methods/
https://beomy.github.io/tech/browser/browser-rendering/
https://kwangsunny.tistory.com/42