브라우저 렌더링 과정은 서버에서 받은 HTML, CSS, JavaScript를 해석해서 실제 화면에 보여주는 과정입니다.
겉으로 보면 브라우저가 HTML 파일을 받아서 바로 화면에 띄우는 것처럼 보이지만, 내부에서는 꽤 많은 일을 합니다.
HTML을 읽어서 문서 구조를 만들고, CSS를 읽어서 스타일 규칙을 정리하고, 각 요소가 어디에 얼마나 큰 크기로 배치될지 계산한 다음, 마지막으로 픽셀을 그립니다.
프론트엔드에서 이 과정을 알아야 하는 이유는 단순합니다.
DOM을 자주 바꾸거나, 레이아웃에 영향을 주는 CSS를 많이 변경하거나, 무거운 애니메이션을 만들면 화면이 느려질 수 있기 때문입니다.
브라우저가 화면을 그릴 때는 보통 아래 흐름으로 이해하면 됩니다.
면접에서는 이 순서를 그대로 외우기보다, “구조를 만들고, 스타일을 붙이고, 위치를 계산하고, 그리고, 합성한다” 정도로 이해해두면 말하기가 훨씬 편합니다.
브라우저는 HTML 문서를 위에서 아래로 읽으면서 태그를 해석합니다.
그리고 그 결과를 트리 구조인 DOM(Document Object Model) 으로 만듭니다.
예를 들어 이런 HTML이 있다고 해보겠습니다.
<body>
<h1>Hello</h1>
<p>Browser Rendering</p>
</body>
브라우저는 body 아래에 h1, p가 있는 구조로 DOM Tree를 만듭니다.
우리가 JavaScript에서 document.querySelector()나 appendChild() 같은 API로 조작하는 대상도 바로 이 DOM입니다.
즉, DOM은 HTML 문자열을 브라우저가 다루기 쉬운 객체 구조로 바꿔놓은 것이라고 볼 수 있습니다.
CSS도 HTML처럼 브라우저가 파싱합니다.
이때 만들어지는 구조가 CSSOM(CSS Object Model) 입니다.
CSSOM은 “어떤 요소에 어떤 스타일이 적용되는지”를 계산하기 위해 필요합니다.
CSS에는 상속, 명시도, 우선순위, 미디어 쿼리 같은 규칙이 있기 때문에 브라우저가 이를 정리해두어야 합니다.
h1 {
color: blue;
font-size: 24px;
}
이 CSS를 읽으면 브라우저는 h1에 적용할 색상과 글자 크기 정보를 CSSOM 안에 정리합니다.
여기서 중요한 점은 DOM만 있어서는 화면을 제대로 그릴 수 없다는 것입니다.
문서 구조는 DOM이 알려주고, 화면에 어떻게 보일지는 CSSOM이 알려줍니다.
브라우저는 DOM과 CSSOM을 합쳐서 Render Tree를 만듭니다.
Render Tree는 실제 화면에 그릴 요소만 모아둔 트리입니다.
예를 들어 display: none이 적용된 요소는 화면에 그리지 않으므로 Render Tree에 들어가지 않습니다.
반면 visibility: hidden은 보이지는 않지만 공간은 차지할 수 있기 때문에 Layout 계산에는 영향을 줄 수 있습니다.
쉽게 말하면 DOM은 문서 전체 구조이고, Render Tree는 그중에서 “진짜 화면에 그릴 대상”에 가깝습니다.
Layout 단계에서는 각 요소의 위치와 크기를 계산합니다.
예를 들어 어떤 박스의 너비가 50%라면, 브라우저는 부모 요소의 크기를 보고 실제 픽셀 너비를 계산해야 합니다.
flex, grid, vh, vw 같은 값도 마찬가지입니다. 코드에는 상대적인 값으로 적혀 있지만, 화면에 그리려면 결국 실제 좌표와 크기가 필요합니다.
Layout은 비용이 큰 작업이 될 수 있습니다.
특히 한 요소의 크기 변화가 부모나 형제 요소에 영향을 주면, 브라우저는 꽤 넓은 범위의 위치와 크기를 다시 계산해야 할 수 있습니다.
Paint 단계에서는 Layout에서 계산한 위치와 크기를 바탕으로 색상, 배경, 텍스트, 이미지, 그림자 같은 시각 요소를 그립니다.
예를 들어 color, background-color, box-shadow 같은 속성이 바뀌면 요소의 크기나 위치는 그대로일 수 있습니다.
이 경우 Layout까지 다시 할 필요는 없지만, 화면에 보이는 색이나 그림자는 다시 그려야 하므로 Paint가 발생할 수 있습니다.
Composite 단계에서는 여러 레이어를 합쳐 최종 화면을 만듭니다.
브라우저는 모든 요소를 한 장의 종이에 한 번에 그리는 방식으로만 처리하지 않습니다.
필요에 따라 화면을 여러 레이어로 나누고, 마지막에 이 레이어들을 합성해서 사용자에게 보여줍니다.
transform, opacity 같은 속성은 Layout이나 Paint를 크게 건드리지 않고 Composite 단계에서 처리될 수 있는 경우가 많습니다.
그래서 위치 이동 애니메이션을 만들 때 top, left보다 transform: translate()를 더 자주 권장합니다.
reflow는 Layout을 다시 계산하는 작업입니다.
요소의 위치나 크기가 바뀌면 브라우저는 “이 요소가 이제 어디에 있어야 하지?”를 다시 계산해야 합니다.
예를 들어 이런 변경은 reflow를 일으킬 수 있습니다.
display 값 변경reflow는 보통 비용이 큰 편입니다.
위치와 크기를 다시 계산하다 보면 주변 요소까지 영향을 받을 수 있기 때문입니다.
repaint는 위치나 크기는 그대로인데, 눈에 보이는 스타일만 다시 그리는 작업입니다.
예를 들어 이런 변경은 repaint를 일으킬 수 있습니다.
visibility 변경repaint는 reflow보다 가벼운 경우가 많지만, 화면의 큰 영역을 다시 그려야 하거나 요소가 많으면 이것도 성능에 영향을 줄 수 있습니다.
JavaScript는 DOM과 CSSOM을 바꿀 수 있기 때문에 렌더링에 직접 영향을 줍니다.
특히 HTML을 파싱하는 중에 일반 <script>를 만나면 브라우저는 스크립트를 다운로드하고 실행할 때까지 HTML 파싱을 멈출 수 있습니다.
스크립트가 DOM을 조작할 수도 있기 때문에, 브라우저 입장에서는 일단 실행 결과를 확인해야 하는 것입니다.
그래서 실무에서는 렌더링 차단을 줄이기 위해 이런 방식을 자주 사용합니다.
defer 또는 async를 사용한다.requestAnimationFrame을 활용한다.예를 들어 스타일을 바꾼 직후 offsetWidth나 getBoundingClientRect()를 읽으면, 브라우저가 최신 레이아웃 값을 알려주기 위해 강제로 Layout을 다시 계산할 수 있습니다.
이런 상황을 반복하면 화면이 버벅일 수 있습니다.
top, left보다 transform, opacity를 먼저 고려합니다.브라우저 렌더링 과정은 HTML, CSS, JavaScript를 해석해서 화면에 보여주는 과정입니다.
먼저 HTML을 파싱해서 DOM Tree를 만들고, CSS를 파싱해서 CSSOM Tree를 만듭니다. 그다음 DOM과 CSSOM을 합쳐 실제로 화면에 그릴 Render Tree를 만들고, 각 요소의 위치와 크기를 계산하는 Layout 단계를 거칩니다. 이후 색상, 배경, 텍스트 등을 그리는 Paint 단계가 있고, 마지막으로 여러 레이어를 합성하는 Composite 단계를 통해 최종 화면이 만들어집니다.
성능 관점에서는 reflow와 repaint를 같이 설명하면 좋습니다.
요소의 크기나 위치가 바뀌면 Layout을 다시 계산하는 reflow가 발생할 수 있고, 색상처럼 시각적인 스타일만 바뀌면 repaint가 발생할 수 있습니다. reflow는 상대적으로 비용이 크기 때문에 DOM 조작을 줄이고, 애니메이션에서는 transform, opacity 같은 속성을 활용하는 것이 도움이 됩니다.
display: none은 Render Tree에서 제외되지만, visibility: hidden은 공간을 차지할 수 있다는 차이를 기억해두면 좋습니다.transform, opacity 중심으로 구현하는 방식을 먼저 떠올려보세요.