브라우저 화면 랜더링 과정

윤효준·2024년 9월 10일
0

콤퓨타 공부

목록 보기
8/17

소프티어 부트 캠프는 현대차에서 진행하는 부트캠프로 수료 후에 채용 면접을 진행한다! 어제(2024-09-09) 그 면접을 진행했다! 면접 중 이 게시글 제목과 관련한 질문을 해주셨는데 모르겠습니다 선언을 했다... 소 잃고 뇌 약간 고치기(???)이긴 하지만 랜더링 과정 어떻게 이루어지는지 한 번 알아보자!!

랜더링이란???

랜더링이란 웹 페이지나 어플리케이션에서 브라우저가 HTML, CSS, JavaScript와 같은 코드를 해석하고 이를 시각적인 화면으로 변환하는 과정을 의미한다. 다시 말해 사용자가 브라우저를 통해 볼 수 있는 웹 페이지를 구성하는 모든 텍스트, 이미지, 스타일 등이 눈에 보이게 만들어지는 작업이다!

랜더링 순서

1. HTML 파싱 및 DOM 트리 생성

1.1 HTML 파일 로드

브라우저는 사용자가 특정 URL을 입력하거나 링크를 클릭하면 해당 웹 페이지의 HTML 파일을 서버에서 요청하여 받는다. 이때 HTML 파일은 브라우저의 네트워크 계층(7계층 설명으로 이동)을 통해 로드된다. HTML 파일이 브라우저로 전송되면 브라우저는 파일을 전송 방식에 따라 스트림 형태로 받거나 완전히 로드된 후 처리할 수 있다. 일반적으로 동적 콘텐츠나 대용량 데이터의 경우 청크(Chunk) 단위로 스트리밍되며, 정적 HTML 파일이나 작은 파일의 경우 완전한 데이터로 한 번에 전달될 수 있다.

여기서 잠깐!!! 스트림 형태란???

스트림은 데이터가 연속적으로 흐르는 방식을 의미한다. 스트리밍 방식은 큰 데이터를 한 번에 전송하지 않고 작은 조각으로 나누어서 순차적으로 전달된다. 이를 통해 수신 측은 데이터를 전부 받을 때까지 기다리지 않고 도착한 데이터부터 차례로 처리할 수 있다.

HTML 파일은 보통 한꺼번에 로드되지 않고 네트워크 상태에 따라 나뉘어 전송되므로 브라우저는 전체 파일을 모두 받을 때까지 기다리지 않고 일부만 로드된 상태에서도 파싱을 시작할 수 있다. 파싱에 대해 간략하게 설명하자면 컴퓨터 프로그램이 입력된 데이터를 분석하고 구조화하는 과정이다. 간략하게 설명드리긴 했지만 파싱에 관한 내용을 읽고 오는 것이 이해하는 데 도움이 많이 될 거 같습니다!

1.2 HTML 파서(parser) 동작

브라우저에는 HTML 파서라는 모듈이 있어 HTML 파일을 해석한다. HTML 파서는 HTML 코드를 한 줄씩 또는 한 글자씩 읽어들이며 그에 따라 문서의 구조를 이해하고 DOM 트리를 구성한다.

파서가 HTML 문서를 해석하는 방식은 트리 생성 모드와 오류 처리 모드 두 가지로 나뉜다. 이 두 가지 모드는 HTML 문서를 처리할 때 파서가 다르게 동작하는 방식이며 각각 HTML의 구조에 따라 다르게 동작한다.


1.2.1 트리 생성 모드(Tree Construction Mode)

트리 생성 모드는 파서가 HTML 문서를 정상적인 HTML 규칙에 따라 해석하여 DOM 트리를 생성하는 기본적인 모드이다. HTML 문서가 올바르게 작성되어 있으면 이 모드에서 파서는 순차적으로 태그를 읽고 DOM 트리를 생성한다.

트리 생성 모드의 동작 과정:

  1. HTML 태그 읽기:

    • 파서는 HTML 문서를 위에서 아래로 한 줄씩 읽으면서 각 태그를 분석한다. HTML 태그는 <로 시작하고 > 끝나는 마크업을 말한다.

    • 예를 들어 <h1>Hello</h1>라는 태그를 읽으면 <h1>은 시작 태그이고 </h1>은 종료 태그이다. 파서는 이를 인식하고 DOM 트리에서 그에 맞는 구조를 만든다.

  2. 토큰 생성(1.3에서 자세히 설명):

    • 파서는 태그를 토큰으로 변환한다. <h1>Hello</h1>에서 파서는 <h1></h1>를 각각 시작 태그와 종료 태그로 변환하고 그 사이의 Hello는 텍스트 노드로 토큰화한다.
  3. DOM 트리 생성:

    • 파서는 각 태그가 DOM 트리의 어느 위치에 있어야 하는지 결정한다. DOM 트리는 HTML 문서의 계층 구조를 나타내는 트리 형태의 데이터 구조이다.

    • 예를 들어 <html>, <head>, <body> 같은 태그들은 상위 요소가 되고 그 안에 속하는 다른 태그들은 하위 요소가 된다.

예시:

<html>
  <body>
    <h1>Hello, World!</h1>
    <p>This is a paragraph.</p>
  </body>
</html>

위의 HTML을 트리 생성 모드로 해석하면 다음과 같은 DOM 트리가 만들어진다.

Document
 └── html
     └── body
         ├── h1
         │   └── "Hello, World!"
         └── p
             └── "This is a paragraph."

1.2.2 오류 처리 모드(Error Handling Mode)

오류 처리 모든느 잘못된 HTML 코드를 파서가 처리하는 방식이다. HTML은 웹의 핵심 기술이기 때문에 웹 페이지의 일관성을 유지하기 위해 어느 정도의 내구성(fault tolerance)이 필요하다. 즉 문법적인 오류가 있더라도 브라우저는 페이지를 정상적으로 렌더링하려고 시도한다.

HTML 문서에서 잘못된 중첩, 빠진 종료 태크, 잘못된 문법 등이 있을 때 파서는 오류 처리 모드로 전환하여 유연하게 DOM 트리를 생성하려고 한다. 이를 오류 복구(error recovery)라고도 한다.

오류 처리 모드의 동작 과정:

  1. 오류 감지:

    • 파서는 HTML 문서를 읽다가 잘못된 부분을 발견하면 이를 인식하고 어떻게 그 오류를 처리할지 결정한다.
    • 예를 들어 <p>태그를 열었는데 닫지 않고 다음 요소로 넘어가는 경우가 이에 해당된다.
  2. 유연한 DOM 트리 생성:

    • 파서는 중첩이 잘못되거나 닫히지 않은 태그를 자동으로 닫아주거나 필요한 경우 자동으로 열어준다.
    • 오류를 무시하거나 파서 내부 규칙에 따라 DOM 트리를 조정하여 웹 페이지가 정상적으로 표시되도록 시도한다.
  3. 태그 자동 닫기:

    • HTML5에서는 자동으로 태그를 닫는 경우가 많다 예를 들어 <p>태그는 자동으로 닫히는 대표적인 예이다.

예시:

<html>
  <body>
    <p>This is a paragraph.
    <p>This is another paragraph.
  </body>
</html>

이 HTML은 문법적으로 <p>태그가 닫히지 않았다. 파서는 이를 인식하고 자동으로 태그를 닫아서 다음과 같이 DOM 트리를 생성한다:

Document
 └── html
     └── body
         ├── p
         │   └── "This is a paragraph."
         └── p
             └── "This is another paragraph."

1.3 문서 토큰화

HTML 파서는 HTML 문서를 토큰화한다. 즉 HTML 코드 내에서 특정 패턴을 발견할 때마다 해당 패턴을 하나의 토큰으로 변환한다. 이 토큰은 HTML 문서의 각 요소를 나타낸다.

예시:

<!DOCTYPE html>
<html>
  <head>
    <title>My Website</title>
  </head>
  <body>
    <h1>Welcome!</h1>
  </body>
</html>

HTML 파서는 위 코드를 읽으면서 <html>, <head>, <title>, <body>, <h1> 등의 태그들을 각각 토큰으로 변환한다.

  • <html> -> HTML element 시작 토큰
  • <head> -> Head element 시작 토큰
  • <title> -> Title element 시작 토큰
  • </title> -> Title element 종료 토큰

이 과정은 문서의 각 태그, 속성, 텍스트 노드 등을 구별하고 이들을 처리할 수 있는 단위로 분리하는 과정이다.

1.4 DOM 트리 생성

토큰화가 완료된 후 파서는 각 토큰을 DOM(Document Object Model)이라는 트리 구조로 변환한다. DOM 트리는 HTML 문서의 계층적 구조를 반영한 트리 형태의 객체이다.

각 HTML 태그는 DOM 트리에서 노드(node)로 표현된다. 노드는 크게 2가지로 나뉜다.

  • 요소 노드(Element Node) : HTML 태그 자체를 의미한다. 예를 들어 <h1>Welcome!</h1><h1>이라는 요소 노드를 생성한다.

  • 텍스트 노드(Text Node) : 태그 안에 있는 텍스트를 나타낸다. <h1>Welcome!</h1>에서 Welcome!은 텍스트 노드로 저장된다.

<!DOCTYPE html>
<html>
  <head>
    <title>Sample Page</title>
  </head>
  <body>
    <h1 id="main-title" class="title-class">Welcome to My Website</h1>
    <img src="image.jpg" alt="Sample Image" />
  </body>
</html>
Document
 └── html
     ├── head
     │   └── title
     │       └── "Sample Page"
     └── body
         ├── h1 (id="main-title", class="title-class")
         │   └── "Welcome to My Website"
         └── img (src="image.jpg", alt="Sample Image")

이처럼 HTML 파일의 각 요소는 DOM 트리에서 특정 위치에 노드로 변환되어 추가된다. 트리 구조의 최상위에는 Document 객체가 있으며 그 하위에는 html, head, body등의 요소들이 위치하게 된다.

1.5 정상적인 요소와 비정상적인 요소 처리

JavaScript는 DOM 트리에 접근하여 그 구조나 내용을 변경할 수 있는 기능을 가지고 있기에 HTML 문서 파싱을 잠시 멈추고 JavaScript 파일이 완전히 로드되고 실행된 후에 파싱을 재개한다.

이를 해결하기 위해 async 또는 defer 속성을 사용하면 JavaScript 파일이 비동기적으로 로드되고 파싱이 중단되지 않도록 할 수 있다.

여기서 잠깐!!! 동기와 비동기란???

동기적이라는 것은 코드를 읽어갈 때 하나의 작업이 끝나야만 다음 작업을 진행한다는 것이다.

반면 비동기적이라는 것은 해당 작업이 끝나지 않았음에도 다음 코드를 수행한다는 것이다! 아래는 async와 defer에 대한 예시인데 동기 비동기를 이해하는 데에도 도움이 될 거 같다!

예시:

<!DOCTYPE html>
<html>
  <body>
    <h1>Hello, World!</h1>
    <script src="script.js" async></script>
    <p>This is a paragraph.</p>
  </body>
</html>
  • <script src="script.js" async>를 사용하면 브라우저는 script.js 파일을 동시에 다운로드하면서 HTML 문서의 나머지 부분도 계속해서 파싱한다.
  • JavaScript 파일이 다운로드되면 HTML 파싱이 중단되고 script.js 파일이 실행된다.
  • 스크립트 실행이 완료되면 HTML 파싱을 재개한다.
<!DOCTYPE html>
<html>
  <body>
    <h1>Hello, World!</h1>
    <script src="script.js" defer></script>
    <p>This is a paragraph.</p>
  </body>
</html>
  • 브라우저는 script.js 파일을 다운로드하면서도 HTML을 계속해서 파싱한다.
  • HTML 문서가 완전히 파싱되고 나면 그때 JavaScript 파일이 실행된다.
  • 이 방식은 파싱이 중단되지 않으므로 페이지 로딩 속도를 더 빠르게 만들 수 있다.
<!DOCTYPE html>
<html>
  <body>
    <h1>Hello, World!</h1>
    <p>This is a paragraph.</p>
    <script src="script.js"></script>
  </body>
</html>
  • 브라우저는 HTML 문서를 순차적으로 파싱하여 DOM 트리를 생성한다.
  • <script>태그를 HTML 문서의 끝에 배치했기 때문에 DOM 트리가 완성된 후에 JavaScript 파일이 다운로드되고 실행된다.
방식동작 방식실행 시점DOM 조작실행 순서사용 상황예시
문서 끝에 <script> 배치HTML 파싱이 끝난 후 JavaScript 파일 다운로드 및 실행DOM 트리 완성 후 실행안전함순서 보장전통적인 방식으로, DOM이 완성된 후에 스크립트가 실행되도록 하고 싶을 때간단한 페이지, DOM 조작
async 속성HTML 파싱과 동시에 비동기적으로 JavaScript 파일 다운로드 및 완료 시 즉시 실행다운로드 완료 시 즉시 실행 (HTML 파싱 중단)DOM 조작에 적합하지 않음순서 보장 안 됨페이지의 성능이 중요하고, 스크립트가 다른 스크립트나 DOM과 상호작용하지 않을 때분석 도구, 광고 스크립트 로드
defer 속성HTML 파싱과 동시에 비동기적으로 JavaScript 파일 다운로드, 문서 파싱 완료 후 실행DOM 트리 완성 후 실행안전함순서 보장스크립트가 DOM 조작을 필요로 하고, DOM 트리가 완성된 후에 실행되어야 할 때DOM 이벤트 핸들러 등록, 페이지 로직 실행

2. CSS 파싱 및 스타일 규칙 적용

2.1 CSS 파일 로드 및 파싱

브라우저는 HTML 문서를 파싱하면서 <link> 또는 <style> 태그를 만나면 css 파일을 로드하고 파싱을 시작한다. 이 과정에서 CSS 코드가 해석되고 CSS의 규칙들이 구조화된다.

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="styles.css">
  </head>
  <body>
    <h1>Hello, World!</h1>
    <p>This is a paragraph.</p>
  </body>
</html>

브라우저는 <link>태그를 만나면 styles.css 파일을 다운로드하고 그 안에 있는 CSS 규칙을 파싱한다.

2.2 CSS 파싱 및 CSSOM 생성

CSS 파일이 로드되면 브라우저는 CSS 코드를 파싱하여 CSSOM이라는 구조화된 객체 모델을 생성한다. 이 과정에서 CSSOM은 각 CSS 선택자와 스타일 속성을 객체로 표현하며 이를 트리 형태로 구조화하여 브라우저가 각 요소에 적용할 스타일을 쉽게 계산할 수 있게 한다.

body {
  background-color: lightblue;
}

h1 {
  color: navy;
  font-size: 2em;
}

p {
  color: gray;
}

이 CSS 파일은 브라우저에 의해 파싱되어 다음과 같은 CSSOM 트리가 생성된다.

CSSOM
 ├── body
 │    └── background-color: lightblue;
 ├── h1
 │    ├── color: navy;
 │    └── font-size: 2em;
 └── p
      └── color: gray;

이 트리 구조는 각 CSS 선택자를 노드(node)로 표현하며 선택자에 적용된 스타일 속성은 해당 선택자 노드의 자식 노드(child node)로 표현된다.

CSS 파싱 시 오류 허용(fault-tolerant) 방식

CSS 파서는 오류 허용(fault-tolerant) 방식을 채택하고 있어 HTML의 파서와는 다르게 오류를 수정하려 하지 않고 무시한다. 잘못된 구문이나 닫히지 않은 대괄호가 있을 경우 해당 CSS 규칙만 무시하고 나머지 올바른 규칙들은 정상적으로 파싱한다.

예시:

body {
    color: red;
    background-color: yellow;

h1 {
    color: blue;
}

위와 같은 CSS에서 body 블록이 닫히지 않았기 때문에 body 스타일은 무시되지만 h1 스타일은 정상적으로 적용된다. 이는 CSS가 다양한 브라우저 환경에서의 호환성을 높이는 중요한 특징 중 하나이다.

2.3 스타일 규칙 적용 과정

CSSOM 트리를 통해 스타일 규칙이 각 HTML 요소에 어떻게 적용되는지를 살펴보자! CSS의 상속, 우선순위 그리고 캐스케이딩 규칙에 따라 스타일이 결정된다.

1. 상속(Inheritance)

CSS에서는 일부 속성들이 부모 요소에서 자식 요소로 상속된다. 예를 들어 color 속성은 상속되는 속성 중 하나다. 따라서 부모 요소에 color가 정의되어 있으면 자식 요소에도 동일한 색상이 적용된다.

2. 우선순위(Specificity)

같은 요소에 여러 CSS 규칙이 적용될 수 있을 때는 우선 순위에 따라 스타일이 결정된다. 더 구체적인 선택자가 높은 우선 순위를 가진다.

h1 {
  color: blue;
}

#main-title {
  color: red;
}

ID 선택자(#main-title)이 더 구체적이므로 color:red;가 적용된다.

3. 캐스케이딩(Cascading)

캐스케이딩은 여러 스타일 규칙이 충돌할 때 원래 선언된 순서에 따라 최종 스타일이 결정되는 규칙이다. 동일한 우선순위를 가진 규칙일 경우 나중에 선언된 스타일이 적용된다.

p {
  color: green;
}

p {
  color: blue;
}

여기서 p 요소의 색상은 color: blue;가 적용된다.

3. DOM 트리와 CSSOM 트리 결합(렌더 트리 생성)

브라우저가 HTML을 파싱해 생성한 DOM 트리와 CSS를 파싱해 생성한 CSSOM 트리는 결합되어 렌더 트리(Render Tree)를 만든다. 렌더 트리는 실제로 화면에 표시될 요소만을 포함하며 스타일 규칙을 각 DOM 요소에 적용하는 역할을 한다.

  • <head>, <title> 같은 요소들은 문서의 구조적인 정보를 담고 있지만 화면에 직접적으로 표시되지 않기 때문에 렌더 트리에 포함되지 않는다.

  • display: none; 스타일이 적용된 요소는 화면에 표시되지 않기 때문에 렌더 트리에 포함되지 않는다.

예시:

<!DOCTYPE html>
<html>
  <head>
    <title>Sample Page</title>
    <link rel="stylesheet" href="styles.css">
  </head>
  <body>
    <h1>Hello, World!</h1>
    <p>This is a paragraph.</p>
  </body>
</html>
//styles.css

body {
  background-color: lightblue;
}

h1 {
  color: navy;
  font-size: 2em;
}

p {
  color: gray;
}

DOM 트리

Document
 └── html
     ├── head
     │   ├── title
     │   │   └── "Sample Page"
     └── body
         ├── h1
         │   └── "Hello, World!"
         └── p
             └── "This is a paragraph."

CSSOM 트리

CSSOM
 ├── body
 │    └── background-color: lightblue;
 ├── h1
 │    ├── color: navy;
 │    └── font-size: 2em;
 └── p
      └── color: gray;

렌더 트리

Render Tree
 ├── body (background-color: lightblue)
 ├── h1 (color: navy, font-size: 2em)
 └── p (color: gray)

4. 레이아웃

레이아웃 과정은 브라우저가 렌더 트리를 바탕으로 각 요소의 크기와 위치를 계산하는 단계이다. 이 과정은 웹 페이지가 사용자에게 시각적으로 어떻게 배치될지를 결정하는 중요한 단계로 레이아웃을 통해 요소가 화면의 어느 위치에 그려질지 크기는 얼마나 될지 결정된다.

4.1 레이아웃의 주요과정

4.1.1 렌더 트리 기반으로 요소들의 초기 위치 및 크기 계산

렌더 트리는 DOM과 CSSOM을 결합하여 화면에 표시할 요소들만 포함한 트리이다. 브라우저는 이 렌더 트리를 바탕으로 요소의 위치와 크기를 계산하기 시작한다. 이 단계에서는 각 요소의 기본적인 배치 규칙에 따라 화면에 배치된다.

주요 배치 규칙

  • 블록 레벨 요소 : div, p, h1 등과 같은 블록 레벨 요소수직으로 배치되며 기본적으로 전체 너비를 차지한다.

  • 인라인 요소 : span, a, strong 등 인라인 요소는 수평으로 배치되며 기본적으로 자신이 포함된 콘텐츠의 최소 너비만 차지한다.

  • CSS 레이아웃 속성: position, float, display 등의 속성에 따라 요소들이 서로 다른 레이아웃 규칙을 따르게 된다.

4.1.2 box-sizing에 따른 계산 방식

1. box-sizing: content-box (기본값)

  • 콘텐츠 영역의 너비(width)만 설정한 값으로 계산됩니다.
  • 패딩, 테두리, 마진은 별도로 더해져 요소의 전체 크기를 결정합니다.
div {
  width: 200px; /* 콘텐츠 영역의 너비 */
  padding: 10px;
  border: 5px solid black;
  margin: 20px;
  box-sizing: content-box; /* 기본값 */
}
  • 전체 너비 계산:
    • 콘텐츠 영역: 200px
    • 패딩: 10px × 2 = 20px
    • 테두리: 5px × 2 = 10px
    • 마진: 20px × 2 = 40px
    • 총 너비: 200 + 20 + 10 + 40 = 270px

2. box-sizing: border-box

  • 설정한 width 값이 전체 요소의 크기를 의미합니다.
  • 패딩과 테두리가 콘텐츠 영역 내에서 차감됩니다.
div {
  width: 200px; /* 요소의 전체 너비 */
  padding: 10px;
  border: 5px solid black;
  margin: 20px;
  box-sizing: border-box;
}
  • 전체 너비 계산:
    • 설정된 width: 200px (이미 패딩과 테두리가 포함됨)
    • 마진은 별도로 적용되므로 양쪽 40px을 더해줌
    • 총 너비: 200 + 40 (마진) = 240px

4.1.3 콘텐츠 및 자식 요소들에 대한 레이아웃 연산

부모 요소의 레이아웃이 결정되면 그 안에 포함된 자식 요소들의 레이아웃도 결정된다. 이때 자식 요소는 부모 요소의 크기와 위치를 기반으로 상대적으로 배치된다.

상속과 레이아웃 전파

  • 부모 요소가 레이아웃을 마치면 자식 요소의 위치와 크기가 상속된다. 부모 요소의 크기가 변경되면 자식 요소의 레이아웃도 재계산될 수 있다.

레이아웃 과정의 종류

  1. 일반적인 흐름 레이아웃(Normal Flow): 기본적인 레이아웃 방식으로 요소들이 순차적으로 배치된다. 블록 레벨 요소는 위에서 아래로 쌓이고 인라인 요소는 왼쪽에서 오른쪽으로 배치된다.

  2. 플렉스박스 레이아웃(Flexbox Layout): 플렉스 컨테이너가 플렉스 아이템을 유연하게 배치하는 레이아웃 방식이다. 컨테이너 안에 있는 아이템들이 자동으로 정렬되고 아이템 간 간격과 크기를 쉽게 조정할 수 있다.

    .container {
     display: flex;
     justify-content: space-between;
    }
  3. 그리드 레이아웃(Grid Layout): 2차원 그리드를 기반으로 요소를 배치한다. 그리드 시스템을 사용해 복잡한 레이아웃을 효육적으로 구현할 수 있다.

    .grid-container {
     display: grid;
     grid-template-columns: 100px 100px 100px;
     grid-gap: 10px;
    }
  4. 포지셔닝 레이아웃(Positioning Layout): position 속성을 사용하여 요소를 정확한 위치에 배치하는 방식이다. 요소는 static(기본값), relative(원래 자신의 위치에 대한 상대적 위치), absolute(static이 아닌 가장 가까운 부모를 기준으로 설정) 또는 fixed(브라우저를 기준으로 설정) 위치에 배치할 수 있다.

레이아웃 최적화: 리플로우(Reflow)

리플로우는 레이아웃 단계에서 발생하는 과정으로 HTML 요소들의 크기와 위치가 변경될 때 브라우저가 전체 페이지 또는 일부 요소들의 레이아웃을 다시 계산하는 작업이다.

리플로우가 발생하는 상황

  1. 요소의 크기 변경

    • 요소의 width, height, padding, margin 속성 변경
  2. 요소의 위치 변경

    • position, top, left, right, bottom 속성 변경
  3. DOM 구조 변경

    • 새로운 요소를 추가하거나 삭제할 때
  4. 브라우저 창 크기 조정

    • 브라우저 창의 크기가 조정되면 전체 레이아웃이 다시 계산된다.
  5. 폰트 크기 변경

    • css에서 font-size를 변경하거나 사용자가 페이지에서 폰트 크기를 확대/축소할 때 텍스트의 크기 변화로 인해 레이아웃이 변경된다.

리플로우 성능 영향

리플로우는 한 요소만 다시 계산하는 것이 아닌 연관된 요소들과 전체 문서 흐름까지 고려해야 하는 작업이므로 비용이 많이 든다. 특히 페이지 내 요소들이 복잡한 레이아웃을 가지고 있고 요소들이 서로 종속적일 때 리플로우가 발생하면 전체 레이아웃을 다시 계산해야 하므로 성능에 큰 영향을 미칠 수 있다.

리플로우의 성능 최적화 방법:
  1. DOM 변경 최소화
    DOM은 브라우저에서 직접 화면에 그려지는 요소들로 이 요소들의 변경은 리플로우를 일으키는 주요 원인이다. 따라서 DOM을 여러 번 변경하는 것보다 한 번에 처리하는 것이 성능에 더 좋다.

     const element = document.querySelector('.box');
    
    // 여러 번의 DOM 업데이트
    element.style.width = '100px';
    element.style.height = '200px';
    element.style.backgroundColor = 'blue';
    
    // 스타일을 한 번에 업데이트
    element.style.cssText = 'width: 100px; height: 200px; background-color: blue;';
  2. 레이아웃 변경 최소화
    레이아웃 변경은 브라우저가 요소들의 크기와 위치를 다시 계산하기 때문에 비용이 많이 드는 작업이다.

    const element = document.querySelector('.box');
    
    // 레이아웃에 영향을 미치는 속성 변경
    element.style.width = '300px';
    element.style.padding = '20px';
    
    // 레이아웃에 영향을 미치지 않는 transform 사용
    element.style.transform = 'scale(1.2)';

    transform 속성을 사용하면 GPU를 이용해 처리되며 리플로우를 발생시키지 않고 성능에 더 좋은 영향을 준다.

  3. 오프스크린 요소 처리
    화면에 보이지 않는 요소는 브라우저가 렌더링할 필요가 없으므로 오프스크린(offscreen) 상태에서 작업을 처리하는 것이 성능에 유리하다. 요소를 화면에 그리기 전에 필요한 스타일이나 레이아웃 변경을 먼저 처리한 후 화면에 표시하는 것이 효율적이다.

    const element = document.querySelector('.box');
    
    // 요소를 화면에 표시된 상태에서 변경
    element.style.display = 'block';  // 요소가 즉시 표시됨
    element.style.width = '300px';    // 리플로우 발생
    element.style.height = '400px';   // 또다시 리플로우 발생
    
    // 요소를 숨긴 상태에서 변경 처리
    element.style.display = 'none';   // 화면에서 일시적으로 숨김
    element.style.width = '300px';
    element.style.height = '400px';
    
    // 변경 작업 후 다시 화면에 표시
    element.style.display = 'block';
  4. 배치 순서 최적화
    브라우저는 DOM 요소의 크기 또는 위치를 읽거나 쓰는 작업을 수행할 때 현재 레이아웃을 기반으로 다시 계산해야 한다. 읽기와 쓰기를 혼합해서 처리하면 리플로우가 자주 발생할 수 있다.

    const element = document.querySelector('.box');
    
    // 읽기와 쓰기를 혼합
    const width = element.offsetWidth;  // 읽기 (레이아웃 정보 접근)
    element.style.width = width + 50 + 'px';  // 쓰기
    const height = element.offsetHeight;  // 다시 읽기
    element.style.height = height + 50 + 'px';  // 다시 쓰기
    
    // 읽기와 쓰기 작업을 분리
    const width = element.offsetWidth;
    const height = element.offsetHeight;
    
    // 읽기 작업 후에 쓰기 작업을 수행
    element.style.width = width + 50 + 'px';
    element.style.height = height + 50 + 'px';
  5. CSS 애니메이션에서 GPU활용
    브라우저는 GPU를 사용하여 애니메이션을 처리할 수 있는데 특히 transform이나 opacity같은 속성은 레이아웃을 다시 계산하지 않으면서 애니메이션을 수행할 수 있으므로 성능에 큰 이점이 있다.

     .box {
      width: 100px;
      transition: width 1s ease;
    }
    
    .box:hover {
      width: 300px;  /* 레이아웃 변경 (리플로우 발생) */
    }
    
    .box:hover {
      transform: scale(1.5);  /* GPU에서 처리 (리플로우 없 음) */
    }

GPU와 CPU의 처리 기준

구분GPU 처리CPU 처리
레이아웃 변경 여부레이아웃에 영향을 주지 않음레이아웃에 영향을 줌
픽셀 기반 처리 가능 여부픽셀 단위로 화면에 표시되는 변화를 처리요소의 크기나 위치 등을 기반으로 레이아웃 재계산
레벨의 차이저수준의 그래픽 작업 (화면에 어떻게 그려질지 담당)상위 레벨의 레이아웃 및 DOM 트리 계산

GPU 사용의 장점

장점설명
빠른 그래픽 처리그래픽 작업에 특화되어 이미지, 애니메이션, 3D 그래픽, 비디오 렌더링 등을 병렬 처리로 빠르게 수행
리플로우/리페인트 최소화transformopacity를 사용하면 리플로우/리페인트가 발생하지 않음, 성능 최적화에 도움
애니메이션 최적화GPU 기반 애니메이션이 더 부드럽고 효율적, 특히 CSS 애니메이션이나 전환에 적합

5. 페인트(Painting) 및 컴포지팅(Compositing)

페인트와 컴포지팅은 브라우저의 렌더링 엔진에서 웹 페이지를 화면에 그리는 마지막 과정에서 발생하는 작업들이다. 이 과정들은 렌더 트리가 준비된 후 각 요소가 실제로 화면에 그려지고 결합되어 최종적으로 시작적인 출력을 생성하는 과정이다.

5.1 페인트

페인트는 렌더 트리에서 각 요소의 시각적 속성을 계산하여 실제 픽셀로 그리는 과정이다. 브라우저는 요소의 배경색, 텍스트, 테두리, 그림자 등의 스타일을 사용하여 각 요소를 화면에 어떻게 그릴지를 결정한다.

5.2 페인트의 주요 과정

5.2.1 텍스트 페인팅

텍스트가 포함된 요소의 폰트, 색상을 기반으로 텍스트가 그려진다.

5.2.2 배경과 테두리 페인팅

요소의 배경색, 배경 이미지, 테두리가 그려진다.

5.2.3 그림자 및 기타 효과

요소에 적용된 박스 그림자, 텍스트 그림자와 같은 스타일도 페인팅 단계에서 적용된다.

리페인팅

리페인팅은 웹 페이지의 요소들이 화면에 다시 그려지는 과정을 이야기한다. 리플로우와 달리 레이아웃을 변경하지 않고 요소의 시각적 속성이 변경뙬 때 발생하는 작업이다.

리페인팅이 발생하는 상황

  1. 색상 변경

    .box {
     color: red; /* 텍스트 색상 변경 (리페인팅 발생) */
     background-color: yellow; /* 배경색 변경 (리페인팅 발생) */
    }
  2. 테두리 변경

    .box {
     border: 2px solid blue; /* 테두리 색상/스타일 변경 (리페인팅 발생) */
    }
  3. 그림자 추가

    .box {
     box-shadow: 5px 5px 10px gray; /* 그림자 추가 (리페인팅 발생) */
    }
    
  4. 배경 이미지 변경

    .box {
     background-image: url('image.jpg'); /* 배경 이미지 변경 (리페인팅 발생) */
    }
    
  5. 불투명도 변경

    .box {
     opacity: 0.5; /* 투명도 변경 (리페인팅 발생) */
    }
    

리플로우와 리페인팅의 관계

구분리플로우 (Reflow)리페인팅 (Repaint)
발생 원인요소의 레이아웃이 변경될 때 (크기, 위치, 마진, 패딩 등)요소의 시각적 속성이 변경될 때 (색상, 배경, 테두리 등)
영향을 받는 요소해당 요소와 관련된 모든 요소 (부모, 자식, 형제 요소)해당 요소만
성능 비용성능 비용이 큼 (리플로우가 자주 발생하면 성능에 큰 영향)성능 비용이 상대적으로 적지만, 자주 발생하면 성능 저하 가능
리페인팅과의 관계리플로우가 발생하면 리페인팅이 자동으로 발생할 수 있음리페인팅은 리플로우를 유발하지 않음
예시width, height, margin, padding, position 변경background-color, border, box-shadow, opacity 변경

5.3 컴포지팅

컴포지팅은 페인트된 레이어들을 결합하여 최종 화면을 만드는 과정이다. 웹 페이지는 여러 개의 레이어로 나뉘어 있고 각 레이어는 독립적으로 그려진다. 컴포지팅은 각 레이어를 적절한 순서대로 합성하여 최종 화면을 만드는 작업이다.

5.4 컴포지팅의 주요 과정

5.4.1 레이어 분리

레이아웃 계산 이후, 브라우저는 렌더 트리에서 레이어를 분리한다. 주로 GPU 가속을 위한 요소들이 별도의 레이어로 분리된다.

5.4.2 레이어가 분리되는 상황

  1. transform, opacity: 이 속성들은 레이아웃을 변경하지 않기 때문에 브라우저는 이를 별도의 레이어로 처리하여 GPU에서 독립적으로 작업할 수 있다.

  2. position: fixed 화면에서 고정된 위치에 있는 요소는 스크롤과 상관없이 항상 동일한 위치에 있어야 하기 때문에 별도의 레이어로 처리된다.

  3. 애니메이션 또는 CSS 전환: 애니메이션이 적용된 요소는 독립적인 레이어로 분리되어 GPU에서 처리되며 성능이 최적화된다.

5.4.3 레이어 결합

페인트 작업이 완료되면 각 레이어가 순차적으로 적층된다. 이 과정에서 레이어의 순서와 투명도 등을 고려하여 화면에 표시되는 최종 이미지를 만든다.

컴포지팅 예시:

<div class="box">Hello, World!</div>

<style>
  .box {
    background-color: lightblue;
    transform: translateX(100px); /* GPU에서 처리되는 컴포지팅 작업 */
  }
</style>

여기서 transform 속성은 요소의 위치를 변경하지만 레이아웃을 변경하지 않으므로 GPU에서 처리되는 별도의 컴포지팅 레이어로 처리될 수 있다. 브라우저는 해당 요소를 독립적인 레이어로 처리하고 전체 페이지가 그려질 때 다른 레이어들과 합성한다.

이게 랜더링 과정이다!

profile
작은 문제를 하나하나 해결하며, 누군가의 하루에 선물이 되는 코드를 작성해 갑니다.

0개의 댓글