πŸ–±οΈ [지도-μΊ”λ²„μŠ€ 연동] 마우슀 이벀트 κ΅¬ν˜„ κ³Όμ •

μ •ν˜œμΈΒ·2024λ…„ 11μ›” 30일
0

πŸ–±οΈ ν”„λ‘œμ νŠΈ

지도λ₯Ό λ Œλ”λ§ν•˜λŠ” μ›Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ 마우슀 이벀트λ₯Ό ν™œμš©ν•΄ μžμ—°μŠ€λŸ¬μš΄ λ“œλž˜κ·Έ 이동과 쀌 인/쀌 아웃을 κ΅¬ν˜„ν•˜λŠ” 것이 λͺ©ν‘œμ˜€μŠ΅λ‹ˆλ‹€.

μ‚¬μš©μž κ²½ν—˜(UX)을 높이기 μœ„ν•΄ λ‹€μ–‘ν•œ 마우슀 λ™μž‘μ„ μ •ν™•ν•˜κ³  μžμ—°μŠ€λŸ½κ²Œ μ²˜λ¦¬ν•˜λŠ” 데 쀑점을 λ‘μ—ˆλŠ”λ°,

μ•„λž˜λŠ” κ΅¬ν˜„ κ³Όμ •μ—μ„œμ˜ μ‹œλ„μ™€ 문제 ν•΄κ²° 방식을 μ •λ¦¬ν•œ λ‚΄μš©μž…λ‹ˆλ‹€.

지도와 μΊ”λ²„μŠ€λ₯Ό μ—°λ™ν•˜λŠ” 과정에 λŒ€ν•œ ν¬μŠ€νŒ…μ€ μ—¬κΈ° μ—μ„œ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€!!!!


🎯 λͺ©ν‘œ

  • 마우슀 λ“œλž˜κ·Έ: 지도와 μΊ”λ²„μŠ€λ₯Ό ν•¨κ»˜ 이동.
  • 마우슀 휠: 쀌 인/쀌 μ•„μ›ƒμ˜ λΆ€λ“œλŸ¬μš΄ μ „ν™˜.
  • λ°˜μ‘μ„±: μ‚¬μš©μžμ˜ λͺ¨λ“  λ™μž‘μ„ λΉ λ₯΄κ²Œ 반영.

πŸ› οΈ κ΅¬ν˜„ κ³Όμ •

1. 기본 이벀트 처리

μ²˜μŒμ—λŠ” mousedown, mousemove, mouseup 이벀트λ₯Ό μ‚¬μš©ν•΄ λ“œλž˜κ·Έλ₯Ό κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€. λ“œλž˜κ·Έ μ‹œμž‘ μ‹œ μœ„μΉ˜λ₯Ό μ €μž₯ν•˜κ³ , mousemoveμ—μ„œ μ’Œν‘œλ₯Ό μ—…λ°μ΄νŠΈν•˜λ„λ‘ ν–ˆμŠ΅λ‹ˆλ‹€.

const handleMouseDown = (e: React.MouseEvent) => {
  const rect = canvasRef.current?.getBoundingClientRect();
  setDragStartPos({
    x: e.clientX - rect.left,
    y: e.clientY - rect.top,
  });
};

const handleMouseMove = (e: React.MouseEvent) => {
  if (!dragStartTime) return;

  const timeElapsed = Date.now() - dragStartTime;
  if (timeElapsed > 300 && !isDragging) setIsDragging(true);

  if (isDragging) {
    // λ“œλž˜κ·Έμ— λ”°λ₯Έ 지도 이동 처리
  }
};

문제

  • λ“œλž˜κ·Έ μ‹œμž‘ 인식 지연: 마우슀 클릭 ν›„ μ•½κ°„μ˜ 지연이 λ°œμƒ.
  • λΆ€μ •ν™•ν•œ μ’Œν‘œ: λ“œλž˜κ·Έ 도쀑 마우슀 μœ„μΉ˜μ™€ μ‹€μ œ 이동 μ’Œν‘œκ°€ 뢈일치.

2. 휠 이벀트 μΆ”κ°€

쀌 인/쀌 아웃은 wheel 이벀트λ₯Ό ν™œμš©ν–ˆμŠ΅λ‹ˆλ‹€. μ΄λ²€νŠΈμ—μ„œ deltaY 값을 읽어 μ€Œμ„ μ‘°μ •ν–ˆμŠ΅λ‹ˆλ‹€.

const handleWheel = (e: React.WheelEvent) => {
  const zoomChange = e.deltaY < 0 ? 1 : -1;
  map.setZoom(map.getZoom() + zoomChange);
  redrawCanvas();
};

문제

  • 쀌 μ „ν™˜ 속도: 휠 슀크둀이 λ„ˆλ¬΄ λ―Όκ°ν•˜κ²Œ λ°˜μ‘ν•΄ 쀌이 κ³Όλ„ν•˜κ²Œ λ³€ν™”.
  • 쀑심 μ’Œν‘œ 문제: 쀌 인/쀌 아웃 μ‹œ 지도가 쀑심 μ’Œν‘œλ₯Ό λ²—μ–΄λ‚˜λŠ” 경우 λ°œμƒ.

3. μžμ—°μŠ€λŸ¬μš΄ λ“œλž˜κ·Έ κ΅¬ν˜„

λ“œλž˜κ·Έμ˜ 지연 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ μ‹œκ°„ 기반 쑰건을 μΆ”κ°€ν–ˆμŠ΅λ‹ˆλ‹€. ν΄λ¦­ν•œ 지 0.3초 이상 κ²½κ³Ό ν›„ isDragging μƒνƒœλ₯Ό true둜 μ„€μ •ν•΄ λ“œλž˜κ·Έλ‘œ κ°„μ£Όν–ˆμŠ΅λ‹ˆλ‹€.

const handleMouseDown = (e: React.MouseEvent) => {
  setDragStartTime(Date.now());
  setDragStartPos({ x: e.clientX, y: e.clientY });
};

const handleMouseMove = (e: React.MouseEvent) => {
  if (!dragStartTime) return;

  const timeElapsed = Date.now() - dragStartTime;
  if (timeElapsed > 300 && !isDragging) setIsDragging(true);

  if (isDragging) {
    // λ“œλž˜κ·Έ 이동 처리
    map.panBy(new naver.maps.Point(dragDeltaX, dragDeltaY));
  }
};

κ°œμ„ μ 

  • λ“œλž˜κ·Έκ°€ μ˜λ„μΉ˜ μ•Šκ²Œ λ°œμƒν•˜λŠ” 문제 ν•΄κ²°.
  • 지도 이동이 더 μžμ—°μŠ€λŸ½κ²Œ μž‘λ™.

4. 쀌 쀑심 μ’Œν‘œ 보정

쀌 인/쀌 μ•„μ›ƒμ˜ 쀑심 μ’Œν‘œ 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ 휠 이벀트 λ°œμƒ μœ„μΉ˜λ₯Ό κΈ°μ€€μœΌλ‘œ 쀑심을 μž¬κ³„μ‚°ν–ˆμŠ΅λ‹ˆλ‹€.

const handleWheel = (e: React.WheelEvent) => {
  const { offsetX, offsetY } = e.nativeEvent;

  // 휠 λ°©ν–₯에 λ”°λ₯Έ 쀌 λ³€κ²½
  const zoomChange = e.deltaY < 0 ? 1 : -1;
  const zoom = map.getZoom() + zoomChange;

  // 이벀트 μ’Œν‘œλ₯Ό μ€‘μ‹¬μœΌλ‘œ 보정
  const latLng = projection.fromOffsetToCoord(new naver.maps.Point(offsetX, offsetY));
  map.setCenter(latLng);
  map.setZoom(zoom);

  redrawCanvas();
};

κ°œμ„ μ 

  • 쀌 인/쀌 아웃 μ‹œ ν™”λ©΄ 쀑심이 μ›€μ§μ΄λŠ” 문제 ν•΄κ²°.
  • μ‚¬μš©μžκ°€ μ§κ΄€μ μœΌλ‘œ μ›ν•˜λŠ” μœ„μΉ˜μ— μ΄ˆμ μ„ 맞좜 수 μžˆλ„λ‘ κ°œμ„ .

🚩 λ°œμƒν•œ 이슈

μΊ”λ²„μŠ€ μœ„μ—μ„œ λ“œλž˜κ·Έ λ™μž‘μ„ 감지해 지도λ₯Ό μ΄λ™μ‹œν‚€κ±°λ‚˜, νŠΉμ • λ™μž‘μ„ κ΅¬ν˜„ν•˜λ €κ³  ν•  λ•Œ λ‹€μŒκ³Ό 같은 λ¬Έμ œκ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€

  1. λ“œλž˜κΉ… λ™μž‘ 감지가 λΆˆμ™„μ „
    λ“œλž˜κ·Έ μ‹œμž‘ μ‘°κ±΄μ΄λ‚˜ 타이밍이 λ§žμ§€ μ•Šμ•„ λ“œλž˜κ·Έκ°€ μ‹œμž‘λ˜μ§€ μ•Šκ±°λ‚˜, μ˜λ„ν•˜μ§€ μ•Šμ€ λ™μž‘μ„ μœ λ°œν•¨.
  2. 지도 μœ„ UI μš”μ†Œμ˜ λ“œλž˜κΉ… κ°„μ„­
    넀이버 지도 APIκ°€ μ œκ³΅ν•˜λŠ” UI μš”μ†Œ(μ €μž‘κΆŒ ν‘œκΈ°, 둜고 λ“±)κ°€ λ“œλž˜κ·Έ 이벀트λ₯Ό λ°©ν•΄ν•˜λ©° μ˜ˆμƒμΉ˜ λͺ»ν•œ κ²°κ³Όλ₯Ό μ΄ˆλž˜ν•¨.
  3. React μƒνƒœ 관리와 DOM μ‘°μž‘ 비동기성
    React μƒνƒœ μ—…λ°μ΄νŠΈμ™€ DOM λ³€κ²½ 타이밍 κ°„μ˜ 뢈일치둜 인해 이벀트 μ²˜λ¦¬κ°€ μ›ν™œν•˜μ§€ μ•Šμ•˜μŒ.

1️⃣ λ“œλž˜κ·Έ μ‹œμž‘ 쑰건과 흐름 μ œμ–΄

λ“œλž˜κΉ…μ„ κ°μ§€ν•˜κΈ° μœ„ν•΄ isDragging μƒνƒœλ₯Ό λ„μž…ν–ˆμŠ΅λ‹ˆλ‹€.

const [isDragging, setIsDragging] = useState(false);

// 마우슀 ν•Έλ“€λŸ¬
const handleMouseDown = () => {
  setTimeout(() => setIsDragging(true), 300); // 0.3초 λ™μ•ˆ 클릭을 μœ μ§€ν•˜λ©΄ λ“œλž˜κΉ… μƒνƒœλ‘œ μ „ν™˜
};

const handleMouseUp = () => setIsDragging(false);

// μŠ€νƒ€μΌ λ³€κ²½
useEffect(() => {
  canvasRef.current.style.pointerEvents = isDragging ? 'none' : 'auto';
}, [isDragging]);

문제점:

  • isDragging μƒνƒœκ°€ λ³€κ²½λ˜λ”λΌλ„ 이미 μ‹œμž‘λœ 마우슀 λ“œλž˜κ·Έ λ™μž‘μ€ μ¦‰μ‹œ 영ν–₯을 받지 λͺ»ν•¨.
  • React μƒνƒœ 관리 νŠΉμ„±μƒ DOM μ—…λ°μ΄νŠΈκ°€ λΉ„λ™κΈ°μ μœΌλ‘œ 이루어져 타이밍 문제 λ°œμƒ.

2️⃣ λ“œλž˜κ·Έ 타이밍과 DOM μ‘°μž‘ 문제 ν•΄κ²°

React의 μƒνƒœ 관리 비동기성을 ν”Όν•˜κΈ° μœ„ν•΄ useRefλ₯Ό μ‚¬μš©ν•˜μ—¬ isDragging μƒνƒœλ₯Ό κ΄€λ¦¬ν•˜κ³ , DOM μ‘°μž‘μ„ 직접 μ²˜λ¦¬ν•˜λŠ” λ°©μ‹μœΌλ‘œ λ³€κ²½ν–ˆμŠ΅λ‹ˆλ‹€.

const isDraggingRef = useRef(false);

const handleMouseDown = () => {
  isDraggingRef.current = true;
  canvasRef.current.style.pointerEvents = 'none';
};

const handleMouseUp = () => {
  isDraggingRef.current = false;
  canvasRef.current.style.pointerEvents = 'auto';
};

문제점:

  • isDraggingRefλ₯Ό ν™œμš©ν•΄λ„ λ“œλž˜κ·Έκ°€ μ˜λ„λŒ€λ‘œ κ°μ§€λ˜μ§€ μ•ŠλŠ” κ²½μš°κ°€ λ°œμƒ.
  • μƒνƒœλ₯Ό ref둜 κ΄€λ¦¬ν•˜λ©΄μ„œ React μƒνƒœ μ—…λ°μ΄νŠΈμ™€μ˜ 좩돌 λ°œμƒ.

3️⃣ Pointer Events κΈ°λ³Έκ°’ λ³€κ²½

λ“œλž˜κ·Έ 문제λ₯Ό λ°˜λŒ€λ‘œ μ ‘κ·Όν•˜μ—¬, 기본적으둜 μΊ”λ²„μŠ€μ˜ pointer-eventsλ₯Ό 'none'으둜 μ„€μ •ν•˜κ³  클릭 μ‹œ 'auto'둜 λ³€κ²½ν•˜λŠ” 방식을 μ‹œλ„ν–ˆμŠ΅λ‹ˆλ‹€.

canvasRef.current.style.pointerEvents = 'none';

const handleClick = () => {
  canvasRef.current.style.pointerEvents = 'auto';
};

문제점:

  • 클릭 λ™μž‘λ„ λ“œλž˜κ·Έλ‘œ κ°„μ£Όλ˜μ–΄ 지도가 μ΄λ™ν•˜μ§€ μ•ŠμŒ.
  • μ‚¬μš©μž κ²½ν—˜μ΄ 떨어지고 클릭 λ™μž‘κ³Ό λ“œλž˜κΉ… λ™μž‘μ˜ 경계가 λͺ¨ν˜Έν•΄μ§.

4️⃣ 넀이버 지도 UI μš”μ†Œ κ°„μ„­ 제거

넀이버 지도 API의 κΈ°λ³Έ UI μš”μ†Œκ°€ λ“œλž˜κ·Έ 이벀트λ₯Ό λ°©ν•΄ν•˜λŠ” 문제λ₯Ό λ°œκ²¬ν–ˆμŠ΅λ‹ˆλ‹€.

이 μš”μ†Œλ“€μ€ div νƒœκ·Έ 내뢀에 μžˆλŠ” span, a, img둜 κ΅¬μ„±λ˜μ–΄ 있으며, 클래슀λͺ…μ΄λ‚˜ IDκ°€ μ—†μ–΄μ„œ μ„ νƒν•˜κΈ° μ–΄λ €μ› μŠ΅λ‹ˆλ‹€.

CSSλ₯Ό 톡해 κ°•μ œλ‘œ λ“œλž˜κΉ…μ„ λ§‰μ•˜μŠ΅λ‹ˆλ‹€.

span, a, img {
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

문제점:

  • λ“œλž˜κΉ… λ°©ν•΄ μš”μ†Œ 일뢀가 μ—¬μ „νžˆ κ°„μ„­.
  • UI μš”μ†Œλ₯Ό μ™„μ „νžˆ μ œκ±°ν•  수 μ—†μœΌλ©°, 곡식 λ¬Έμ„œμ— λ”°λ₯΄λ©΄ 둜고 μ œκ±°λŠ” λΆˆκ°€λŠ₯함.

πŸ”‘ μ΅œμ’… ν•΄κ²° 방법

μœ„μ˜ μ‹œλ„λ“€μ„ λ°”νƒ•μœΌλ‘œ κ°€μž₯ μ•ˆμ •μ μ΄κ³  효율적인 방식을 λ„μΆœν–ˆμŠ΅λ‹ˆλ‹€.

  1. λ“œλž˜κΉ… λ™μž‘ μ΅œμ ν™”
    • λ“œλž˜κ·Έ μ‹œμž‘κ³Ό μ’…λ£Œ 쑰건을 λͺ…ν™•νžˆ μ •μ˜.
    • pointer-eventsλ₯Ό React μƒνƒœμ™€ λΆ„λ¦¬ν•˜μ—¬ 직접 DOM μ‘°μž‘μœΌλ‘œ 관리.
  2. UI μš”μ†Œ λ“œλž˜κ·Έ κ°„μ„­ 제거
    • API 제곡 UI μš”μ†Œμ˜ λ“œλž˜κΉ… λ°©ν•΄λ₯Ό μ΅œμ†Œν™”ν•˜κΈ° μœ„ν•΄ CSS 기반 접근을 μœ μ§€.
    • 둜고 제거 λŒ€μ‹  μ‚¬μš©μž 선택 방지(user-select: none)둜 λŒ€μ²΄.
  3. React와 DOM μ‘°μž‘μ˜ μ—­ν•  뢄리
    • DOM μ‘°μž‘μ€ 직접 μ ‘κ·Όν•˜λ©° ReactλŠ” μƒνƒœ 관리λ₯Ό μœ„ν•œ μš©λ„λ‘œλ§Œ μ‚¬μš©.

5. μ΅œμ’… μ„ νƒλœ μ½”λ“œμ™€ 이유

μ΅œμ’…μ μœΌλ‘œ μ•„λž˜ μ½”λ“œλ‘œ 마우슀 이벀트λ₯Ό κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€. μ—¬λŸ¬ μ‹œλ„λ₯Ό 톡해 μžμ—°μŠ€λŸ¬μš΄ UXλ₯Ό μ œκ³΅ν•˜λŠ” 데 μ„±κ³΅ν–ˆμŠ΅λ‹ˆλ‹€.

const handleMouseDown = (e: React.MouseEvent) => {
  setDragStartTime(Date.now());
  const rect = canvasRef.current!.getBoundingClientRect();
  setDragStartPos({
    x: e.clientX - rect.left,
    y: e.clientY - rect.top,
  });
};

const handleMouseMove = (e: React.MouseEvent) => {
  if (!dragStartTime) return;

  const timeElapsed = Date.now() - dragStartTime;
  if (timeElapsed > 300 && !isDragging) setIsDragging(true);

  if (isDragging) {
    const rect = canvasRef.current!.getBoundingClientRect();
    const dragEndPos = { x: e.clientX - rect.left, y: e.clientY - rect.top };

    // 지도 이동 처리
    const deltaX = dragStartPos.x - dragEndPos.x;
    const deltaY = dragStartPos.y - dragEndPos.y;
    map.panBy(new naver.maps.Point(deltaX, deltaY));
  }
};

const handleWheel = (e: React.WheelEvent) => {
  const zoomChange = e.deltaY < 0 ? 1 : -1;
  map.setZoom(map.getZoom() + zoomChange);
  redrawCanvas();
};

μ•„λž˜λŠ” 쀌(마우슀 휠)κ³Ό λ“œλž˜κ·Έλ₯Ό κ΅¬ν˜„ν–ˆμ„ λ•Œμ˜ λͺ¨μŠ΅μž…λ‹ˆλ‹€!
(gif 캑처 κ³Όμ •μ—μ„œ 쑰금.... 느리게 캑처된 점 μ–‘ν•΄ λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.....γ… γ… )
(λ§Œμ•½ 이미지가 λ©ˆμΆ°μžˆλ‹€λ©΄, μ΄λ―Έμ§€μ—μ„œ 였λ₯Έμͺ½ 마우슀 클릭 ν›„ μƒˆ νƒ­μ—μ„œ 이미지 μ—΄κΈ°λ‘œ λ“€μ–΄κ°€μ£Όμ„Έμš”... λ²¨λ‘œκ·Έκ°€ gifλ₯Ό μ§€μ›ν•˜μ§€ μ•ŠλŠ”κ°€λ΄μš”....)

0개의 λŒ“κΈ€