소프티어 부트 캠프는 현대차에서 진행하는 부트캠프로 수료 후에 채용 면접을 진행한다! 어제(2024-09-09) 그 면접을 진행했다! 면접 중 이 게시글 제목과 관련한 질문을 해주셨는데 모르겠습니다 선언을 했다... 소 잃고 뇌 약간 고치기(???)이긴 하지만 랜더링 과정 어떻게 이루어지는지 한 번 알아보자!!
랜더링이란 웹 페이지나 어플리케이션에서 브라우저가 HTML, CSS, JavaScript와 같은 코드를 해석하고 이를 시각적인 화면으로 변환하는 과정을 의미한다. 다시 말해 사용자가 브라우저를 통해 볼 수 있는 웹 페이지를 구성하는 모든 텍스트, 이미지, 스타일 등이 눈에 보이게 만들어지는 작업이다!
브라우저는 사용자가 특정 URL을 입력하거나 링크를 클릭하면 해당 웹 페이지의 HTML 파일을 서버에서 요청하여 받는다. 이때 HTML 파일은 브라우저의 네트워크 계층(7계층 설명으로 이동)을 통해 로드된다. HTML 파일이 브라우저로 전송되면 브라우저는 파일을 전송 방식에 따라 스트림 형태로 받거나 완전히 로드된 후 처리할 수 있다. 일반적으로 동적 콘텐츠나 대용량 데이터의 경우 청크(Chunk) 단위로 스트리밍되며, 정적 HTML 파일이나 작은 파일의 경우 완전한 데이터로 한 번에 전달될 수 있다.
스트림은 데이터가 연속적으로 흐르는 방식을 의미한다. 스트리밍 방식은 큰 데이터를 한 번에 전송하지 않고 작은 조각
으로 나누어서 순차적으로 전달된다. 이를 통해 수신 측은 데이터를 전부 받을 때까지 기다리지 않고 도착한 데이터부터 차례로 처리할 수 있다.
HTML 파일은 보통 한꺼번에 로드되지 않고 네트워크 상태에 따라 나뉘어 전송되므로 브라우저는 전체 파일을 모두 받을 때까지 기다리지 않고 일부만 로드된 상태에서도 파싱을 시작할 수 있다. 파싱에 대해 간략하게 설명하자면 컴퓨터 프로그램이 입력된 데이터를 분석하고 구조화하는 과정이다. 간략하게 설명드리긴 했지만 파싱에 관한 내용을 읽고 오는 것이 이해하는 데 도움이 많이 될 거 같습니다!
브라우저에는 HTML 파서
라는 모듈이 있어 HTML 파일을 해석한다. HTML 파서는 HTML 코드를 한 줄씩 또는 한 글자씩 읽어들이며 그에 따라 문서의 구조를 이해하고 DOM 트리
를 구성한다.
파서가 HTML 문서를 해석하는 방식은 트리 생성 모드와 오류 처리 모드 두 가지로 나뉜다. 이 두 가지 모드는 HTML 문서를 처리할 때 파서가 다르게 동작하는 방식이며 각각 HTML의 구조에 따라 다르게 동작한다.
트리 생성 모드는 파서가 HTML 문서를 정상적인
HTML 규칙에 따라 해석하여 DOM 트리
를 생성하는 기본적인 모드이다. HTML 문서가 올바르게 작성되어 있으면 이 모드에서 파서는 순차적으로 태그를 읽고 DOM 트리를 생성한다.
HTML 태그 읽기:
파서는 HTML 문서를 위에서 아래로 한 줄씩 읽으면서 각 태그를 분석한다. HTML 태그는 <
로 시작하고 >
끝나는 마크업을 말한다.
예를 들어 <h1>Hello</h1>
라는 태그를 읽으면 <h1>
은 시작 태그이고 </h1>
은 종료 태그이다. 파서는 이를 인식하고 DOM 트리에서 그에 맞는 구조를 만든다.
토큰 생성(1.3에서 자세히 설명):
<h1>Hello</h1>
에서 파서는 <h1>
와 </h1>
를 각각 시작 태그와 종료 태그로 변환하고 그 사이의 Hello
는 텍스트 노드로 토큰화한다.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."
오류 처리 모든느 잘못된 HTML 코드를 파서가 처리하는 방식이다. HTML은 웹의 핵심 기술이기 때문에 웹 페이지의 일관성을 유지하기 위해 어느 정도의 내구성(fault tolerance)이 필요하다. 즉 문법적인 오류가 있더라도 브라우저는 페이지를 정상적으로 렌더링하려고 시도한다.
HTML 문서에서 잘못된 중첩, 빠진 종료 태크, 잘못된 문법 등이 있을 때 파서는 오류 처리 모드로 전환하여 유연하게 DOM 트리를 생성하려고 한다. 이를 오류 복구(error recovery)라고도 한다.
오류 감지:
<p>
태그를 열었는데 닫지 않고 다음 요소로 넘어가는 경우가 이에 해당된다.유연한 DOM 트리 생성:
태그 자동 닫기:
<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."
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 종료 토큰이 과정은 문서의 각 태그, 속성, 텍스트 노드 등을 구별하고 이들을 처리할 수 있는 단위로 분리하는 과정이다.
토큰화가 완료된 후 파서는 각 토큰을 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
등의 요소들이 위치하게 된다.
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 문서의 나머지 부분도 계속해서 파싱한다.<!DOCTYPE html>
<html>
<body>
<h1>Hello, World!</h1>
<script src="script.js" defer></script>
<p>This is a paragraph.</p>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<h1>Hello, World!</h1>
<p>This is a paragraph.</p>
<script src="script.js"></script>
</body>
</html>
<script>
태그를 HTML 문서의 끝에 배치했기 때문에 DOM 트리가 완성된 후에 JavaScript 파일이 다운로드되고 실행된다.방식 | 동작 방식 | 실행 시점 | DOM 조작 | 실행 순서 | 사용 상황 | 예시 |
---|---|---|---|---|---|---|
문서 끝에 <script> 배치 | HTML 파싱이 끝난 후 JavaScript 파일 다운로드 및 실행 | DOM 트리 완성 후 실행 | 안전함 | 순서 보장 | 전통적인 방식으로, DOM이 완성된 후에 스크립트가 실행되도록 하고 싶을 때 | 간단한 페이지, DOM 조작 |
async 속성 | HTML 파싱과 동시에 비동기적으로 JavaScript 파일 다운로드 및 완료 시 즉시 실행 | 다운로드 완료 시 즉시 실행 (HTML 파싱 중단) | DOM 조작에 적합하지 않음 | 순서 보장 안 됨 | 페이지의 성능이 중요하고, 스크립트가 다른 스크립트나 DOM과 상호작용하지 않을 때 | 분석 도구, 광고 스크립트 로드 |
defer 속성 | HTML 파싱과 동시에 비동기적으로 JavaScript 파일 다운로드, 문서 파싱 완료 후 실행 | DOM 트리 완성 후 실행 | 안전함 | 순서 보장 | 스크립트가 DOM 조작을 필요로 하고, DOM 트리가 완성된 후에 실행되어야 할 때 | DOM 이벤트 핸들러 등록, 페이지 로직 실행 |
브라우저는 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 규칙을 파싱한다.
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) 방식을 채택하고 있어 HTML의 파서와는 다르게 오류를 수정하려 하지 않고 무시한다. 잘못된 구문이나 닫히지 않은 대괄호가 있을 경우 해당 CSS 규칙만 무시하고 나머지 올바른 규칙들은 정상적으로 파싱한다.
예시:
body {
color: red;
background-color: yellow;
h1 {
color: blue;
}
위와 같은 CSS에서 body
블록이 닫히지 않았기 때문에 body
스타일은 무시되지만 h1
스타일은 정상적으로 적용된다. 이는 CSS가 다양한 브라우저 환경에서의 호환성을 높이는 중요한 특징 중 하나이다.
CSSOM 트리를 통해 스타일 규칙이 각 HTML 요소에 어떻게 적용되는지를 살펴보자! CSS의 상속, 우선순위 그리고 캐스케이딩 규칙에 따라 스타일이 결정된다.
CSS에서는 일부 속성들이 부모 요소에서 자식 요소로 상속된다. 예를 들어 color
속성은 상속되는 속성 중 하나다. 따라서 부모 요소에 color가 정의되어 있으면 자식 요소에도 동일한 색상이 적용된다.
같은 요소에 여러 CSS 규칙이 적용될 수 있을 때는 우선 순위에 따라 스타일이 결정된다. 더 구체적인 선택자가 높은 우선 순위를 가진다.
h1 {
color: blue;
}
#main-title {
color: red;
}
ID 선택자(#main-title
)이 더 구체적이므로 color:red;
가 적용된다.
캐스케이딩은 여러 스타일 규칙이 충돌할 때 원래 선언된 순서에 따라 최종 스타일이 결정되는 규칙이다. 동일한 우선순위를 가진 규칙일 경우 나중에 선언된 스타일이 적용된다.
p {
color: green;
}
p {
color: blue;
}
여기서 p
요소의 색상은 color: blue;
가 적용된다.
브라우저가 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;
}
Document
└── html
├── head
│ ├── title
│ │ └── "Sample Page"
└── body
├── h1
│ └── "Hello, World!"
└── p
└── "This is a paragraph."
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)
레이아웃 과정은 브라우저가 렌더 트리를 바탕으로 각 요소의 크기와 위치를 계산하는 단계이다. 이 과정은 웹 페이지가 사용자에게 시각적으로 어떻게 배치될지를 결정하는 중요한 단계로 레이아웃을 통해 요소가 화면의 어느 위치에 그려질지 크기는 얼마나 될지 결정된다.
렌더 트리는 DOM과 CSSOM을 결합하여 화면에 표시할 요소들만 포함한 트리이다. 브라우저는 이 렌더 트리를 바탕으로 요소의 위치와 크기를 계산하기 시작한다. 이 단계에서는 각 요소의 기본적인 배치 규칙에 따라 화면에 배치된다.
블록 레벨 요소 : div
, p
, h1
등과 같은 블록 레벨 요소는 수직으로 배치되며 기본적으로 전체 너비를 차지한다.
인라인 요소 : span
, a
, strong
등 인라인 요소는 수평으로 배치되며 기본적으로 자신이 포함된 콘텐츠의 최소 너비만 차지한다.
CSS 레이아웃 속성: position
, float
, display
등의 속성에 따라 요소들이 서로 다른 레이아웃 규칙을 따르게 된다.
box-sizing: content-box
(기본값)width
)만 설정한 값으로 계산됩니다.div {
width: 200px; /* 콘텐츠 영역의 너비 */
padding: 10px;
border: 5px solid black;
margin: 20px;
box-sizing: content-box; /* 기본값 */
}
box-sizing: border-box
width
값이 전체 요소의 크기를 의미합니다.div {
width: 200px; /* 요소의 전체 너비 */
padding: 10px;
border: 5px solid black;
margin: 20px;
box-sizing: border-box;
}
width
: 200px (이미 패딩과 테두리가 포함됨)부모 요소의 레이아웃이 결정되면 그 안에 포함된 자식 요소들의 레이아웃도 결정된다. 이때 자식 요소는 부모 요소의 크기와 위치를 기반으로 상대적으로 배치된다.
일반적인 흐름 레이아웃(Normal Flow): 기본적인 레이아웃 방식으로 요소들이 순차적으로 배치된다. 블록 레벨 요소는 위에서 아래로 쌓이고 인라인 요소는 왼쪽에서 오른쪽으로 배치된다.
플렉스박스 레이아웃(Flexbox Layout): 플렉스 컨테이너가 플렉스 아이템을 유연하게 배치하는 레이아웃 방식이다. 컨테이너 안에 있는 아이템들이 자동으로 정렬되고 아이템 간 간격과 크기를 쉽게 조정할 수 있다.
.container {
display: flex;
justify-content: space-between;
}
그리드 레이아웃(Grid Layout): 2차원 그리드를 기반으로 요소를 배치한다. 그리드 시스템을 사용해 복잡한 레이아웃을 효육적으로 구현할 수 있다.
.grid-container {
display: grid;
grid-template-columns: 100px 100px 100px;
grid-gap: 10px;
}
포지셔닝 레이아웃(Positioning Layout): position
속성을 사용하여 요소를 정확한 위치에 배치하는 방식이다. 요소는 static(기본값)
, relative(원래 자신의 위치에 대한 상대적 위치)
, absolute(static이 아닌 가장 가까운 부모를 기준으로 설정)
또는 fixed(브라우저를 기준으로 설정)
위치에 배치할 수 있다.
리플로우는 레이아웃 단계에서 발생하는 과정으로 HTML 요소들의 크기와 위치가 변경될 때 브라우저가 전체 페이지 또는 일부 요소들의 레이아웃을 다시 계산하는 작업이다.
요소의 크기 변경
width
, height
, padding
, margin
속성 변경요소의 위치 변경
position
, top
, left
, right
, bottom
속성 변경DOM 구조 변경
브라우저 창 크기 조정
폰트 크기 변경
font-size
를 변경하거나 사용자가 페이지에서 폰트 크기를 확대/축소할 때 텍스트의 크기 변화로 인해 레이아웃이 변경된다.리플로우는 한 요소만 다시 계산하는 것이 아닌 연관된 요소들과 전체 문서 흐름까지 고려해야 하는 작업이므로 비용이 많이 든다. 특히 페이지 내 요소들이 복잡한 레이아웃을 가지고 있고 요소들이 서로 종속적일 때 리플로우가 발생하면 전체 레이아웃을 다시 계산해야 하므로 성능에 큰 영향을 미칠 수 있다.
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;';
레이아웃 변경 최소화
레이아웃 변경은 브라우저가 요소들의 크기와 위치를 다시 계산하기 때문에 비용이 많이 드는 작업이다.
const element = document.querySelector('.box');
// 레이아웃에 영향을 미치는 속성 변경
element.style.width = '300px';
element.style.padding = '20px';
// 레이아웃에 영향을 미치지 않는 transform 사용
element.style.transform = 'scale(1.2)';
transform 속성을 사용하면 GPU를 이용해 처리되며 리플로우를 발생시키지 않고 성능에 더 좋은 영향을 준다.
오프스크린 요소 처리
화면에 보이지 않는 요소는 브라우저가 렌더링할 필요가 없으므로 오프스크린(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';
배치 순서 최적화
브라우저는 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';
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 처리 |
---|---|---|
레이아웃 변경 여부 | 레이아웃에 영향을 주지 않음 | 레이아웃에 영향을 줌 |
픽셀 기반 처리 가능 여부 | 픽셀 단위로 화면에 표시되는 변화를 처리 | 요소의 크기나 위치 등을 기반으로 레이아웃 재계산 |
레벨의 차이 | 저수준의 그래픽 작업 (화면에 어떻게 그려질지 담당) | 상위 레벨의 레이아웃 및 DOM 트리 계산 |
장점 | 설명 |
---|---|
빠른 그래픽 처리 | 그래픽 작업에 특화되어 이미지, 애니메이션, 3D 그래픽, 비디오 렌더링 등을 병렬 처리로 빠르게 수행 |
리플로우/리페인트 최소화 | transform 과 opacity 를 사용하면 리플로우/리페인트가 발생하지 않음, 성능 최적화에 도움 |
애니메이션 최적화 | GPU 기반 애니메이션이 더 부드럽고 효율적, 특히 CSS 애니메이션이나 전환에 적합 |
페인트와 컴포지팅은 브라우저의 렌더링 엔진에서 웹 페이지를 화면에 그리는 마지막 과정에서 발생하는 작업들이다. 이 과정들은 렌더 트리가 준비된 후 각 요소가 실제로 화면에 그려지고 결합되어 최종적으로 시작적인 출력을 생성하는 과정이다.
페인트는 렌더 트리에서 각 요소의 시각적 속성을 계산하여 실제 픽셀로 그리는 과정이다. 브라우저는 요소의 배경색, 텍스트, 테두리, 그림자 등의 스타일을 사용하여 각 요소를 화면에 어떻게 그릴지를 결정한다.
텍스트가 포함된 요소의 폰트, 색상을 기반으로 텍스트가 그려진다.
요소의 배경색, 배경 이미지, 테두리가 그려진다.
요소에 적용된 박스 그림자, 텍스트 그림자와 같은 스타일도 페인팅 단계에서 적용된다.
리페인팅은 웹 페이지의 요소들이 화면에 다시 그려지는 과정을 이야기한다. 리플로우와 달리 레이아웃을 변경하지 않고 요소의 시각적 속성이 변경뙬 때 발생하는 작업이다.
색상 변경
.box {
color: red; /* 텍스트 색상 변경 (리페인팅 발생) */
background-color: yellow; /* 배경색 변경 (리페인팅 발생) */
}
테두리 변경
.box {
border: 2px solid blue; /* 테두리 색상/스타일 변경 (리페인팅 발생) */
}
그림자 추가
.box {
box-shadow: 5px 5px 10px gray; /* 그림자 추가 (리페인팅 발생) */
}
배경 이미지 변경
.box {
background-image: url('image.jpg'); /* 배경 이미지 변경 (리페인팅 발생) */
}
불투명도 변경
.box {
opacity: 0.5; /* 투명도 변경 (리페인팅 발생) */
}
구분 | 리플로우 (Reflow) | 리페인팅 (Repaint) |
---|---|---|
발생 원인 | 요소의 레이아웃이 변경될 때 (크기, 위치, 마진, 패딩 등) | 요소의 시각적 속성이 변경될 때 (색상, 배경, 테두리 등) |
영향을 받는 요소 | 해당 요소와 관련된 모든 요소 (부모, 자식, 형제 요소) | 해당 요소만 |
성능 비용 | 성능 비용이 큼 (리플로우가 자주 발생하면 성능에 큰 영향) | 성능 비용이 상대적으로 적지만, 자주 발생하면 성능 저하 가능 |
리페인팅과의 관계 | 리플로우가 발생하면 리페인팅이 자동으로 발생할 수 있음 | 리페인팅은 리플로우를 유발하지 않음 |
예시 | width , height , margin , padding , position 변경 | background-color , border , box-shadow , opacity 변경 |
컴포지팅은 페인트된 레이어들을 결합하여 최종 화면을 만드는 과정이다. 웹 페이지는 여러 개의 레이어로 나뉘어 있고 각 레이어는 독립적으로 그려진다. 컴포지팅은 각 레이어를 적절한 순서대로 합성하여 최종 화면을 만드는 작업이다.
레이아웃 계산 이후, 브라우저는 렌더 트리에서 레이어를 분리한다. 주로 GPU 가속을 위한 요소들이 별도의 레이어로 분리된다.
transform
, opacity
: 이 속성들은 레이아웃을 변경하지 않기 때문에 브라우저는 이를 별도의 레이어로 처리하여 GPU에서 독립적으로 작업할 수 있다.
position: fixed
화면에서 고정된 위치에 있는 요소는 스크롤과 상관없이 항상 동일한 위치에 있어야 하기 때문에 별도의 레이어로 처리된다.
애니메이션 또는 CSS 전환: 애니메이션이 적용된 요소는 독립적인 레이어로 분리되어 GPU에서 처리되며 성능이 최적화된다.
페인트 작업이 완료되면 각 레이어가 순차적으로 적층된다. 이 과정에서 레이어의 순서와 투명도 등을 고려하여 화면에 표시되는 최종 이미지를 만든다.
컴포지팅 예시:
<div class="box">Hello, World!</div>
<style>
.box {
background-color: lightblue;
transform: translateX(100px); /* GPU에서 처리되는 컴포지팅 작업 */
}
</style>
여기서 transform
속성은 요소의 위치를 변경하지만 레이아웃을 변경하지 않으므로 GPU에서 처리되는 별도의 컴포지팅 레이어로 처리될 수 있다. 브라우저는 해당 요소를 독립적인 레이어로 처리하고 전체 페이지가 그려질 때 다른 레이어들과 합성한다.
이게 랜더링 과정이다!