- Published on
리액트 단에서 미디어 쿼리 구현하기
- Authors
- Name
- CDD
서론

Media Query
란 해상도에 따라 UI를 다르게 보여주고 싶을 때 사용하는 기술입니다. 위의 움짤을 보시면 구글 로고가 가로폭에 따라 다르게 렌더링 되는 것을 보실수가 있을겁니다. 이를 통해 모바일 뷰와 데스크탑 뷰를 구분해서 보여주기도 하고, 레이아웃이 깨지는 것을 방지하기도 합니다.
미디어 쿼리 구현하기
일반적으로 Media Query
를 구현하는 방법은 아래와 같습니다.
@media screen and (max-width: 600px) {
.logo {
width: 100px;
}
} // 600px 이하일 때
@media screen and (min-width: 601px) {
.logo {
width: 200px;
}
} // 601px 이상일 때
@media
키워드를 활용하여 screen
이라는 미디어 타입을 지정하고, max-width
와 min-width
를 통해 해상도에 따라 다른 스타일을 적용할 수 있습니다. 간단하긴 하지만, 이를 리액트에서 도입한다고 하면 고민이 생깁니다. 리렌더링 기반으로 동작하는 리액트에서 특정 태그의 width
를 알아내 동적으로 로직을 처리해야 했거든요.
ResizeObserver
일반적으로 생각해보면 누군가가 만들어놓은 훅이나 라이브러리 등을 사용하는 것이 일반적이겠지만, 어느정도 실구현을 해보면서 감을 좀 익혀보려고 합니다. 바로 ResizeObserver
를 사용하는 방법인데요, 윈도우 혹은 Element
의 리사이징을 감지하여 로직을 처리하려고 합니다.
1. Ref 선언하기
그러기 위해서는 먼저 Ref
를 선언해줘야 합니다. useRef
를 사용하여 이를 특정 Element
에 연결해줍니다. 하는 김에 width
를 트래킹 할 수 있는 state
도 선언해줍니다.
const sectionRef = useRef<HTMLDivElement>(null);
const [sectionWidth, setSectionWidth] = useState(null);
return (
<section ref={sectionRef}>
<Gallery />
</section>
);
2. useEffect를 통한 ResizeObserver 등록
느낌 상 intersectionObserver
와 비슷한 느낌이 드는데, 이러한 것들은 useEffect
를 통해 등록해주는 식으로 사용합니다. 아래의 예시 코드를 보시죠.
useEffect(() => {
const handleResize = () => {
if (sectionRef.current) {
setWidth(sectionRef.current.offsetWidth);
console.log("width = ", sectionRef.current.offsetWidth);
}
};
// 요소의 크기를 관찰하는 ResizeObserver 생성
const resizeObserver = new ResizeObserver(() => {
handleResize();
});
// 참조된 요소에 대해 ResizeObserver를 연결
if (sectionRef.current) {
resizeObserver.observe(sectionRef.current);
}
// 컴포넌트가 언마운트되거나 리렌더링될 때 옵저버 해제
return () => {
resizeObserver.disconnect();
};
}, []);
Ref
객체의 offsetWidth
에 접근하면 해당 Element
의 너비를 알 수 있는데, ResizeObserver
를 통해 너비의 변경을 감지하고, 트래킹 하기 위해 만든 state
인 sectionWidth
에 너비를 업데이트 해줍니다. 그리고 handleResize
함수에서 콘솔을 한 번 찍어봤는데요,

역시 잘 나오는 것을 확인할 수 있었습니다. 뭐, 이후의 로직은 여러분들의 몫이죠. 저 같은 경우 특정 width
보다 줄어들면 버튼을 더보기 메뉴로 옮기는 식의 로직을 짰는데요, 간단하게 코드로 보여드리면 이런 느낌입니다.
const DropDownMenu = useMemo(() => {
if (containerWidth < 600) {
return (
["메뉴1", "메뉴2", "메뉴3", "메뉴4", "메뉴5"]
)
}
return (
["메뉴1", "메뉴2", "메뉴3"]
)
}, [containerWidth])
그렇게 구현하니 아래와 같이 잘 동작하는 것을 확인할 수 있었습니다.
