ResizeObserver
특정 요소의 크기가 바뀔 때 이를 감지해야 하는 상황이 있다. 가장 먼저 떠오르는 방법은 window.resize 이벤트다.
window.addEventListener("resize", () => {
// 브라우저 창 크기가 바뀔 때만 호출됨
});
그런데 이 방식은 브라우저 창 크기가 바뀔 때만 동작한다. 사이드바를 여닫거나, 다른 요소가 확장/축소되면서 특정 컨테이너의 크기가 바뀌는 경우에는 resize 이벤트가 발생하지 않는다. 브라우저 창 자체의 크기는 그대로이기 때문이다.
ResizeObserver는 이 문제를 해결한다. 특정 DOM 요소의 크기 변화를 직접 관찰하기 때문에, 원인에 관계없이 해당 요소의 크기가 바뀌면 콜백이 실행된다.
기본 사용법
ResizeObserver는 생성, 관찰 등록, 관찰 중지 세 단계로 사용한다.
// 1. Observer 생성
const observer = new ResizeObserver(callback);
// 2. 관찰할 요소 등록
observer.observe(targetElement);
// 3. 관찰 중지
observer.unobserve(targetElement);
observer.disconnect(); // 모든 요소 관찰 중지
IntersectionObserver와 사용 패턴이 동일하다. observe()로 여러 요소를 등록할 수 있고, unobserve()로 특정 요소만, disconnect()로 모든 요소의 관찰을 중지한다.
콜백 함수
관찰 중인 요소의 크기가 변하면 콜백이 실행된다. 콜백은 entries 배열을 인자로 받는데, 각 entry에서 해당 요소의 크기 정보를 확인할 수 있다.
const callback = (entries) => {
entries.forEach((entry) => {
const { width, height } = entry.contentRect;
console.log(`크기 변경: ${width} x ${height}`);
});
};
contentRect는 padding을 제외한 콘텐츠 영역의 크기를 반환한다. 더 정밀한 크기가 필요하면 borderBoxSize나 contentBoxSize를 사용할 수 있다.
| 속성 | 설명 |
|---|---|
contentRect | 콘텐츠 영역의 위치/크기 (DOMRectReadOnly) |
contentBoxSize | 콘텐츠 박스 크기 (padding 제외) |
borderBoxSize | 보더 박스 크기 (border, padding 포함) |
devicePixelContentBoxSize | 디바이스 픽셀 단위의 콘텐츠 박스 크기 |
target | 관찰 중인 DOM 요소 |
window.resize와의 차이
window.resize | ResizeObserver | |
|---|---|---|
| 감지 대상 | 브라우저 창 전체 | 지정한 개별 요소 |
| 사이드바 여닫기 | 감지 못함 | 감지 |
| CSS 레이아웃 변화 | 감지 못함 | 감지 |
| 다른 요소의 확장/축소 | 감지 못함 | 감지 |
| 브라우저 창 리사이즈 | 감지 | 감지 (요소 크기가 바뀌는 경우) |
크기가 변하는 상황들
ResizeObserver가 감지하는 "크기 변화"는 다양한 원인에서 발생한다.
- 브라우저 창 크기 변경 (이 경우
window.resize도 동작) - CSS 미디어 쿼리에 의한 레이아웃 변경
- 사이드바, 패널 등의 여닫기
- JavaScript로 요소의 스타일을 직접 변경
- 텍스트 콘텐츠가 추가/제거되면서 높이가 바뀌는 경우
- CSS
flex,grid레이아웃에서 다른 자식 요소의 크기 변화로 인한 연쇄 변경
활용 예시
컨테이너 크기에 맞춰 캔버스 리사이징
컨테이너 크기가 바뀔 때 캔버스도 따라서 크기를 조정해야 하는 경우에 유용하다.
const observer = new ResizeObserver((entries) => {
const { width, height } = entries[0].contentRect;
canvas.width = width;
canvas.height = height;
redraw();
});
observer.observe(document.querySelector(".canvas-container"));
반응형 차트
차트 라이브러리를 사용할 때, 컨테이너 크기에 따라 차트를 다시 그려야 하는 경우다.
const observer = new ResizeObserver((entries) => {
const { width, height } = entries[0].contentRect;
chart.resize(width, height);
});
observer.observe(document.querySelector(".chart-container"));
텍스트 오버플로우 감지
요소의 크기가 바뀌면서 텍스트가 넘치는지 확인하고, 넘치면 "더보기" 버튼을 표시하는 패턴이다.
const observer = new ResizeObserver((entries) => {
entries.forEach((entry) => {
const el = entry.target;
const isOverflowing = el.scrollHeight > el.clientHeight;
toggleShowMoreButton(el, isOverflowing);
});
});
observer.observe(document.querySelector(".text-content"));
React에서 사용
React에서는 커스텀 훅으로 만들어 사용하면 편하다.
function useElementSize(ref: RefObject<HTMLElement | null>) {
const [size, setSize] = useState<{ width: number; height: number } | null>(null);
useEffect(() => {
const element = ref.current;
if (!element) return;
const observer = new ResizeObserver(([entry]) => {
const { width, height } = entry.contentRect;
setSize({ width, height });
});
observer.observe(element);
return () => observer.disconnect();
}, [ref.current]);
return size;
}
이 훅을 사용하면 요소의 크기가 바뀔 때마다 size 상태가 업데이트된다. cleanup 함수에서 disconnect()를 호출해서 컴포넌트가 언마운트될 때 메모리 누수를 방지한다.
function MyComponent() {
const containerRef = useRef<HTMLDivElement>(null);
const size = useElementSize(containerRef);
return (
<div ref={containerRef}>
{size && <p>현재 크기: {size.width} x {size.height}</p>}
</div>
);
}
주의사항
ResizeObserver 콜백 안에서 관찰 중인 요소의 크기를 다시 바꾸면 무한 루프가 발생할 수 있다. 브라우저는 이를 방지하기 위해 한 프레임에 하나의 ResizeObserver 콜백만 처리하고, 무한 루프가 감지되면 ResizeObserver loop completed with undelivered notifications 에러를 콘솔에 출력한다. 이 에러 자체는 치명적이지 않지만, 콜백 안에서 크기를 변경하는 로직은 피하는 게 좋다.
브라우저 지원
모든 최신 브라우저에서 지원한다.
- Chrome 64+
- Firefox 69+
- Safari 13.1+
- Edge 79+
정리
window.resize는 브라우저 창 크기만 감지하지만, ResizeObserver는 개별 DOM 요소의 크기 변화를 감지한다.- CSS 레이아웃 변화, 사이드바 여닫기, 다른 요소의 크기 변동 등 원인에 관계없이 크기가 바뀌면 콜백이 실행된다.
- IntersectionObserver와 사용 패턴이 동일해서(observe/unobserve/disconnect), 하나를 알면 다른 하나도 쉽게 쓸 수 있다.