🔗 이전 내용
DOM 렌더링 방식에 따른 성능 비교해보기 - 1
DOM 렌더링 방식에 따른 성능 비교해보기 - 2
📢 들어가며
지난 포스트에서의 내용은 다음과 같다.
- DOM 트리에 새로운 요소를 추가하는 것은 강제 리플로우가 되지 않는다.
- 일반적으로 요소의 좌표 또는 사이즈에 대한 READ 작업 시 강제 리플로우가 발생한다.
🎨 수정 / 삭제 시 강제 리플로우가 발생하는가?
기존 요소를 수정하거나 삭제 시에는 강제 리플로우가 발생하는지를 알아보자.
아래는 간단하게 div 요소 하나만 존재한다. 해당 div의 사이즈와 위치를 변경하는 코드를 추가하였다.
이때, 브라우저가 HTML을 처음 파싱할 때 div 수정 코드까지 한 번에 처리하여 레이아웃을 계산하므로, 실행을 1초 후로 지연시켰다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
width: 10px;
height: 10px;
background-color: red;
}
</style>
</head>
<body>
<div></div>
<script>
const div = document.querySelector('div');
setTimeout(() => {
div.style.width = '100px';
div.style.height = '100px';
div.style.left = '100px';
div.style.top = '100px';
}, 1000);
</script>
</body>
</html>
노란 부분의 Timer fired 부분이 setTimeout의 콜백함수가 호출되는 부분이다. 요소의 스타일을 수정하는 각각의 코드를 추가하였지만 강제 리플로우는 발생하지 않고 이후 한번의 리플로우만 발생하는 것을 확인할 수 있다.

이번에는 요소를 삭제하는 코드를 확인해보자. div와 p 태그를 각각 하나씩 제거하는 코드이다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
width: 10px;
height: 10px;
background-color: red;
}
p {
width: 10px;
height: 10px;
background-color: blue;
}
</style>
</head>
<body>
<div></div>
<p></p>
<script>
const div = document.querySelector('div');
const p = document.querySelector('p');
setTimeout(() => {
div.remove();
p.remove();
}, 1000);
</script>
</body>
</html>
삭제에 대한 결과도 마찬가지로 강제 리플로우가 발생하지 않고, 이후 한번의 리플로우만 발생한다.

[🎊 결론] DOM을 변경할 때는 강제 리플로우가 발생하지 않는다.
[🔥 주의] 강제 리플로우만 발생하지 않고 리플로우는 발생할 수 있다!!
🧵 Dom을 Read 시에 항상 강제 리플로우가 발생하는 것은 아니다
먼저 아래의 코드를 실행 후 결과를 확인해보자.
아래의 코드는 div와 p 요소의 offsetWidth를 조회하는 코드이다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
width: 10px;
height: 10px;
background-color: red;
}
p {
width: 10px;
height: 10px;
background-color: blue;
}
</style>
</head>
<body>
<div></div>
<p></p>
<script>
const div = document.querySelector('div');
const p = document.querySelector('p');
setTimeout(() => {
console.log(div.offsetWidth);
console.log(p.offsetWidth);
}, 1000);
</script>
</body>
</html>
개발자도구에서 강제 리플로우가 발생하는지 여부를 확인해보면 전혀 발생하지 않은 것을 확인할 수 있다.

앞선 포스트에서 offsetWidth 등을 사용해서 Dom의 정보를 읽을 때 강제 리플로우가 발생하는 것을 확인하였다. 그런데 위의 결과가 나오는 이유는 무엇일까?
그 이유는 바로 리플로우가 발생하면 offsetWidth와 같은 레이아웃 정보를 캐시에 저장하고 있기 때문이다. 최초 사이트에 접속하게 되면 HTML 파싱이 완료 된 후 리플로우(레이아웃)가 발생한다. 이 정보를 캐시로 저장하고 있다.
그래서 변경사항이 존재하지 않는 경우에는 캐싱된 정보를 즉시 반환하기 때문에 offsetWidth 정보를 읽어도 리플로우가 발생하지 않는다.
🧵 효율적으로 레이아웃 정보 읽기
아래의 코드는 현재 자신의 width에 10px을 더하는 코드이다. 현재 width 값을 얻어도기 위해 offsetWidth를 활용하고 있다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
p {
width: 10px;
height: 10px;
background-color: blue;
}
</style>
</head>
<body>
<p id="p1"></p>
<p id="p2"></p>
<p id="p3"></p>
<p id="p4"></p>
<script>
const p = document.querySelectorAll('p');
setTimeout(() => {
p.forEach((dom) => {
dom.style.width = `${dom.offsetWidth + 10}px`;
});
}, 1000);
</script>
</body>
</html>
지금까지 우리가 알아보았던 내용을 토대로 리플로우가 몇번 발생할지를 예측해볼 수 있다.
정답은 바로 4번이다.
- 처음 dom.offsetWidth를 조회할 땐 이전 변경사항이 없기 때문에 캐싱된 결과를 사용(강제 리플로우 X)
- 이후 3번의 dom.offsetWidth를 조회할 땐 변경사항(p1 ~ p3를 변경했기 때문)이 존재하기 때문에 강제 리플로우 발생
- 마지막 변경사항(p4의 변경)이 발생한 이후에는 조회가 없기 때문에 강제 리플로우는 발생하지 않고 이후 리플로우가 발생
강제 리플로우는 조회하려는 대상과 무관하게 변경사항이 존재하면 발생하게 된다.
그래서 위에서 p1에서 변경사항이 발생하고, p2의 정보를 조회할 때도 강제 리플로우가 발생하는 것이다.
아래의 이벤트 로그를 보면 총 4번의 레이아웃(리플로우)가 발생한 것을 확인할 수 있다.

위의 코드를 최적화를 통해 레이아웃이 한번만 발생하도록 수정해보자.
위의 코드에서는 기본적으로 각 요소의 width를 변경할 때 다른 요소의 변경된 정보가 필요하지는 않다. 즉, 리플로우가 발생한 뒤에 offsetWidth 정보를 조회할 필요가 없다는 것이다.
레이아웃 정보를 조회할 때는 Dom을 변경하는 코드를 호출하기 이전에 몰아서 작성하는 것이 좋다. 그래야 캐싱된 결과를 사용하여 강제 리플로우를 발생시키지 않을 수 있기 때문이다.
코드를 아래와 같이 변경하였다. 변경사항은 다음과 같다.
- offsetWidth의 값을 Dom 변경 전 모두 조회하도록 수정
- Dom을 변경할 땐 조회된 결과를 참조하도록 수정
const offsetWidths = [...p].map(({ offsetWidth }) => offsetWidth);
p.forEach((dom, index) => {
dom.style.width = `${offsetWidths[index] + 10}px`;
});
수정 된 결과를 보통 리플로우(레이아웃)이 한번만 발생한 것을 확인할 수 있다. 또한 강제 리플로우도 발생하지 않게 되었다.

🎯 결론
- Dom에 대해 Write할 땐 강제 리플로우 발생 X
- 레이아웃 정보를 Read할 때 변경사항이 없다면 캐싱된 결과를 반환
- 레이아웃 정보를 조회할 땐 최적화를 위해 변경사항이 발생하기 전에 조회하기
'Javascript > 성능' 카테고리의 다른 글
| [Javascript - 성능] 리플로우 줄이기 - 3 (requestAnimationFrame - 2) (0) | 2025.07.27 |
|---|---|
| [Javascript - 성능] 리플로우 줄이기 - 2 (requestAnimationFrame - 1) (0) | 2025.07.26 |
| [Javascript - 성능] DOM 렌더링 방식에 따른 성능 비교해보기 - 2 (0) | 2025.06.29 |
| [Javascript - 성능] DOM 렌더링 방식에 따른 성능 비교해보기 - 1 (0) | 2025.06.22 |