Published on

리액트 단에서 미디어 쿼리 구현하기

Authors
  • avatar
    Name
    CDD
    Twitter

서론

Media Query Example

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-widthmin-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를 통해 너비의 변경을 감지하고, 트래킹 하기 위해 만든 statesectionWidth에 너비를 업데이트 해줍니다. 그리고 handleResize 함수에서 콘솔을 한 번 찍어봤는데요,

width capture

역시 잘 나오는 것을 확인할 수 있었습니다. 뭐, 이후의 로직은 여러분들의 몫이죠. 저 같은 경우 특정 width보다 줄어들면 버튼을 더보기 메뉴로 옮기는 식의 로직을 짰는데요, 간단하게 코드로 보여드리면 이런 느낌입니다.

const DropDownMenu = useMemo(() => {
  if (containerWidth < 600) {
    return (
      ["메뉴1", "메뉴2", "메뉴3", "메뉴4", "메뉴5"]
    )
  }
  return (
    ["메뉴1", "메뉴2", "메뉴3"]
  )
}, [containerWidth])

그렇게 구현하니 아래와 같이 잘 동작하는 것을 확인할 수 있었습니다.

Media Query Example