Zustand를 활용한 특정 영역 마우스 커서 위치 제한

JJY·2025년 4월 15일

FRONT

목록 보기
10/11
post-thumbnail

마우스 커서에 스크롤 유도 표시를 따라다니도록 구현을 했다.

요런 식으로 마우스 움직이면 따라다닌다.
혹시 궁금하다면 👇👇
마우스 커서 스크롤 유도 표시

👀 문제상황

잘 따라다니는데 Navbar 위에 마우스가 있을 때는 커서가 pointer되서 겹쳐보인다...

이런식으로....
그래서 Navbar 위에서는 스크롤 유도 표시가 안보이게 해야겠다 생각!!

🌟 아이디어

ref로 영역을 감지하고 해당 영역에 있으면 안보이도록 설정하자 !!
Context나 prop으로 nav DOM을 전달해야 되는데
nav에서 마우스 스크롤 유도 있는 DOM까지 prop drilling을 하기에는
너무 많은 계층을 거쳐야 하고 복잡하다...
그래서 zustand로 nav에 있는지를 전달하기로 결정 !!

🌟 구현

1️⃣ 우선 store 생성 !!

// navStore.ts

import { create } from "zustand";

interface NavState {
  navRect: DOMRect | null;
  setNavRect: (react: DOMRect) => void;
}

export const useNavStore = create<NavState>((set) => ({
  navRect: null,
  setNavRect: (rect) => set({ navRect: rect }),
}));

2️⃣ Nav 컨포넌트에서 nav위치 zustand에 저장

// NavMenu.tsx

import { useRef, useEffect } from "react";
// store 가져오기
import { useNavStore } from "../_store/navStore";

export default function NavMenu() {
  // ref 선언
  const navRef = useRef<HTMLDivElement | null>(null);
  const setNavRect = useNavStore((state) => state.setNavRect);

  useEffect(() => {
    if (navRef.current) {
      const updateNavRect = () => {
        // HTML 요소의 위치와 크기 정보 가져오기
        const rect = navRef.current!.getBoundingClientRect();
        // zustand에 저장
        setNavRect(rect);
      };
      updateNavRect();
      // 창 크기 바뀔 때마다 nav 위치 업데이트
      window.addEventListener("resize", updateNavRect);

      return () => {
        // 컴포넌트가 사라질 때 이벤트 제거
        window.removeEventListener("resize", updateNavRect);
      };
    }
  }, []);

  return (
    <>
      // nav영역 ref 연결
      <ul className={styles.main} ref={navRef}>
        <li className={styles.title}>
          <Link href="/">DDIB</Link>
        </li>
        <li>
         .....
         
        </li>
      </div>
    </>
  );
}

📌 .getBoundingClientRect()란?

HTML 요소의 사각형 정보(=bounding box) 를 반환하는 메서드로 반환값은 DOMRect 객체
rect에 아래와 같은 속성들이 저장된다.

rect = {
  top: number,     // 화면 위쪽에서 요소까지의 거리 (px)
  left: number,    // 화면 왼쪽에서 요소까지의 거리 (px)
  bottom: number,  // top + height
  right: number,   // left + width
  width: number,   // 요소의 너비
  height: number,  // 요소의 높이
}

3️⃣ zustand에서 상태가져와서 스크롤 유도 표시 위치 조정

// MainArea.tsx


import { useState, useEffect } from "react";
import { useNavStore } from "@/app/_store/navStore";

export default function MainArea() {
  // 저장해논 위치 zustand에서 가져오기
  const navRect = useNavStore((state) => state.navRect);
  // 현재 마우스 위치 저장
  const [position, setPosition] = useState({ x: 0, y: 0 });
  // nav위에 있는지 없는지 상태 저장
  const [isOnNav, setIsOnNav] = useState(false);

  useEffect(() => {
    const handleMouseMove = (e: MouseEvent) => {
      setPosition({ x: e.clientX, y: e.clientY });
      if (navRect) {
        // 현재 위치가 nav안에 있는지 체크
        const isInside =
          e.clientX >= navRect.left &&
          e.clientX <= navRect.right &&
          e.clientY >= navRect.top &&
          e.clientY <= navRect.bottom;

        // 영역안에 있으면 useState true로 바꿔준다
        setIsOnNav(isInside);
        
        // 영역안에 있으면 위치를 업데이트 하지 않는다
        // 영역안에 없으면 위치 업데이트 해서 계속 따라다니게
        if (!isInside) {
          // 위치 업데이트
          setPosition({ x: e.clientX, y: e.clientY });
        }
      }
    };

    document.addEventListener("mousemove", handleMouseMove);
    return () => {
      document.removeEventListener("mousemove", handleMouseMove);
    };
  }, [navRect]);

  return (
    <div className={styles.main}>
      {data && (
        <>
          {data.todayNotOverProducts.length != 0 && (
            <MainSlider todayList={data.todayNotOverProducts} onBg={handleBg} />
          )}
          <TodayItems todayList={data.todayProducts} bgColor={bgColor} />
        </>
      )}
      <div
        className={styles.scrollCursor}
        // 스크롤 유도 표시 위치 지정
        // nav안에 있으면 display : none으로 안보이게 설정
        style={{
          left: position.x,
          top: position.y,
          display: isOnNav ? "none" : "block",
        }}
      >
        <div> Scroll</div>
      </div>
    </div>
  );
}

🌟 실행결과

이제 Navbar 위에 마우스를 hover하면


요로케ㅎㅎ
스크롤 유도 표시가 보이지 않고 커서 pointer만 나오는 것을 알 수 있다ㅎㅎㅎ

profile
안녕하세요 :)

0개의 댓글