웹 애플리케이션을 개발하다 보면 특정 DOM 요소나 카드 컴포넌트의 크기가 변경될 때 내부 레이아웃이나 차트를 재선언해야 하는 상황을 경험해봤을 것이다.
다만 대부분의 개발자들은 여전히 window 객체의 resize 이벤트를 바인딩하여 브라우저 전체 창이 바뀔 때만 대응하거나, setInterval 타이머를 돌려 크기를 체크하는 비효율적인 방식을 사용하고 있다.
이번에는 특정 요소의 크기 변화만 정밀하게 감지하는 ResizeObserver API가 정확히 뭔지, 왜 필요한지, 그리고 실무에서 성능 저하 없이 어떻게 활용하는지 완벽하게 정리해서 소개하겠다.

 

1. ResizeObserver 개념과 기존 방식과의 비교

과거에는 특정 div 요소의 크기 변화를 감지할 수 있는 전용 이벤트가 존재하지 않았다. 그래서 개발자들은 브라우저 전체 창 크기를 감지하는 window.addEventListener('resize', ...)에 의존하곤 했다.
하지만 사이드바가 열리거나, CSS transition 효과로 컨테이너 크기가 바뀌거나, 동적으로 내부 콘텐츠가 추가되어 요소의 크기만 변경되는 경우에는 window resize 이벤트가 호출되지 않는다.
ResizeObserver API는 창 크기와 상관없이 지정한 특정 DOM 엘리먼트의 크기 변화를 관찰(Observe)하여 콜백을 실행해주는 브라우저 내장 API다.

 

비교 항목window.onresizeResizeObserver API
감지 대상브라우저 뷰포트(전체 창)개별 DOM 요소 (div, section 등)
감지 시점창 크기 변경 시에만 작동사이드바 토글, DOM 변경, CSS 애니메이션 등 요소 크기 변경 시 작동
성능 영향불필요한 전체 레이아웃 리플로우 유발요소의 변형만 효율적으로 렌더링 주기에 맞춰 관찰
메모리 관리전역 이벤트로 해제 미흡 시 누수 위험disconnect() 및 unobserve()로 세밀한 해제 가능

 

2. ResizeObserver 기본 사용 방법

ResizeObserver는 생성이 매우 간단하다. 인스턴스를 생성할 때 콜백 함수를 전달하고, observe() 메서드로 대상 요소를 등록하기만 하면 된다.

 

기본 구문과 관찰 메서드

기본적으로 아래 3가지 메서드를 사용해 동작을 제어한다.

  • observer.observe(targetElement): 특정 요소의 크기 관찰 시작
  • observer.unobserve(targetElement): 특정 요소의 관찰 중단
  • observer.disconnect(): 모든 관찰 대상 해제

 

3. 실전 예제: 반응형 컨테이너 크기 감지 및 적용

실무에서 자주 발생하는 '부모 요소 크기에 맞춰 내부 차트나 텍스트를 재계산하는 상황'을 코드로 비교해 보자.

 

✗ 잘못된 코드 (window resize 이벤트 오용)

✗ window resize 이벤트에만 의존하면 사이드바 토글 등으로 부모 요소를 줄였을 때 크기 변경을 전혀 감지하지 못한다.

// ✗ 잘못된 방식: 창 크기가 변할 때만 작동함
const cardElement = document.querySelector('.responsive-card');

window.addEventListener('resize', () => {
  // 창 크기 변경 시에만 실행되므로, 사이드바 토글로 .responsive-card 크기만 바뀔 땐 반응하지 않음
  const width = cardElement.clientWidth;
  console.log('카드 너비:', width);
});

 

✓ 올바른 코드 (ResizeObserver 사용)

✓ ResizeObserver를 사용하면 어떤 이유로든 해당 요소의 크기가 변하는 즉시 실행된다.

// ✓ 올바른 방식: ResizeObserver로 특정 요소만 관찰
const cardElement = document.querySelector('.responsive-card');

const resizeObserver = new ResizeObserver((entries) => {
  for (const entry of entries) {
    // entry.contentRect를 통해 정확한 크기 정보 획득
    const { width, height } = entry.contentRect;
    console.log(`카드 너비: ${width}px, 높이: ${height}px`);

    // 조건부 클래스 변경이나 차트 re-render 호출
    if (width < 400) {
      entry.target.classList.add('is-small');
    } else {
      entry.target.classList.remove('is-small');
    }
  }
});

// 관찰 시작
resizeObserver.observe(cardElement);

 

결과 / 출력값

요소의 너비가 400px 미만으로 줄어드는 순간 관찰자가 감지하여 즉시 is-small 클래스가 추가되고 콘솔에 실시간 크기가 출력된다. 창 크기를 조절하지 않고 CSS transition으로 요소를 줄여도 정상 동작한다.

 

4. 주의사항 및 흔히 하는 실수

ResizeObserver를 사용할 때 개발자들이 가장 자주 저지르는 두 가지 실수가 있다.

 

1) 콜백 내부에서 관찰 대상의 크기를 다시 변경하는 무한 루프

콜백 함수 안에서 entry.target.style.width = '100px' 형태로 관찰 중인 요소의 크기를 직접 바꾸면 ResizeObserver가 다시 호출되어 무한 루프에 빠진다. 이 경우 브라우저 콘솔에 ResizeObserver loop limit exceeded 에러가 발생한다.
✗ 관찰 대상의 inline style 크기를 콜백에서 직접 수정하지 말고, class를 변경하거나 CSS flex/grid를 활용해야 한다.

 

2) 메모리 누수 (DOM 제거 시 disconnect 미호출)

SPA(React, Vue, Vanilla JS SPA) 환경에서 컴포넌트가 화면에서 제거(Unmount)될 때 관찰을 해제하지 않으면 메모리 누수가 일어난다.
✓ DOM 요소를 삭제하기 전 반드시 observer.disconnect()를 호출하여 관찰을 종료하자.

// ✓ 메모리 정리 패턴 예시
function setupComponent() {
  const target = document.querySelector('#widget');
  const observer = new ResizeObserver(handleResize);
  observer.observe(target);

  // 컴포넌트 파괴 시 실행할 cleanup 함수
  return () => {
    observer.disconnect();
  };
}

 

5. 마무리 및 요약

ResizeObserver API는 반응형 웹 애플리케이션에서 요소 단위 레이아웃 최적화의 핵심이다. 창 크기 이벤트에만 의존하던 구식 방식에서 벗어나는 작은 습관이 모여서 웹 앱의 반응성과 성능을 비약적으로 끌어올린다는 점을 잊지 말자. 이 글의 실전 예제 코드를 참고해 기존 리사이즈 로직을 대체해 보면, 훨씬 부드럽고 효율적인 UI를 얻을 수 있을 것이다.