πŸ“± ν„°μΉ˜ 이벀트둜 μΊ”λ²„μŠ€μ—μ„œ 지도 이동과 ν™•λŒ€/μΆ•μ†Œ κ΅¬ν˜„ν•˜κΈ°: feat. 마우슀 μ΄λ²€νŠΈμ™€μ˜ 차이

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

지도 μƒν˜Έμž‘μš©μ„ κ΅¬ν˜„ν•˜λ©΄μ„œ, μ²˜μŒμ—λŠ” 마우슀 이벀트λ₯Ό ν™œμš©ν•΄ 지도 이동과 ν™•λŒ€/μΆ•μ†Œλ₯Ό κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€.

ν•˜μ§€λ§Œ λͺ¨λ°”일 ν™˜κ²½μ„ μ§€μ›ν•˜κΈ° μœ„ν•΄ ν„°μΉ˜ 이벀트λ₯Ό μΆ”κ°€ν•΄μ•Ό ν–ˆμŠ΅λ‹ˆλ‹€. ν„°μΉ˜ μ΄λ²€νŠΈλŠ” 마우슀 μ΄λ²€νŠΈμ™€ 기본적으둜 λΉ„μŠ·ν•˜μ§€λ§Œ, 일뢀 λ™μž‘κ³Ό 처리 방식이 λ‹¬λžμŠ΅λ‹ˆλ‹€. μ˜ˆμƒν–ˆλ˜ 것보닀 κ΅¬ν˜„ν•΄μ•Ό ν•  둜직이 더 λ§Žμ•˜κ³ , μ—¬λŸ¬ 가지 문제λ₯Ό ν•΄κ²°ν•˜λ©° λ§Žμ€ 고민을 ν•˜κ²Œ λμŠ΅λ‹ˆλ‹€.

이번 ν¬μŠ€νŒ…μ—μ„œλŠ” 마우슀 이벀트둜 κ΅¬ν˜„λœ 지도 μƒν˜Έμž‘μš©μ„ ν„°μΉ˜ 이벀트둜 ν™•μž₯ν•œ 과정을 μž‘μ„±ν•΄λ³΄λ €κ³  ν•©λ‹ˆλ‹€.


🎯 λͺ©ν‘œ

μ œκ°€ μž‘μ—…ν•΄μ•Όν•˜λŠ” μ£Όμš” κΈ°μˆ μ€ μ•„λž˜ 3κ°€μ§€λ‘œ 정리할 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

  1. ν•œ 손가락 ν„°μΉ˜λ‘œ 지도λ₯Ό 이동할 수 있게 ν•˜κΈ°.
  2. 두 손가락 ν„°μΉ˜λ‘œ ν™•λŒ€/μΆ•μ†Œλ₯Ό κ΅¬ν˜„ν•˜κΈ°.
  3. ν„°μΉ˜μ™€ κ΄€λ ¨λœ λͺ¨λ“  λ™μž‘μ΄ μžμ—°μŠ€λŸ½κ³  μ§κ΄€μ μœΌλ‘œ μž‘λ™ν•˜λ„λ‘ μ„€κ³„ν•˜κΈ°.

πŸ–± 마우슀 이벀트둜 κ΅¬ν˜„ν•œ κΈ°μ‘΄ 둜직

이전 ν¬μŠ€νŒ…μ—μ„œ μž‘μ„±ν•œ κ²ƒμ²˜λŸΌ, 마우슀 이벀트둜 지도 이동/μ€Œμ€ κ΅¬ν˜„ν•΄ λ‘” μƒν™©μ΄μ—ˆμŠ΅λ‹ˆλ‹€.

λͺ¨λ°”일이 아닐 λ•Œμ—λŠ” 마우슀 이벀트둜, λͺ¨λ°”μΌλ‘œ μ ‘μ†ν–ˆμ„ λ•Œμ—λŠ” ν„°μΉ˜ 이벀트둜 λͺ¨λ‘ λ™μž‘ν•  수 있게 ν„°μΉ˜ 이벀트λ₯Ό κ΅¬ν˜„ν•΄μ£Όμ–΄μ•Ό ν–ˆμŠ΅λ‹ˆλ‹€.

참고둜 κΈ°μ‘΄ 마우슀 μ΄λ²€νŠΈλŠ” μ•„λž˜μ™€ 같이 κ΅¬ν˜„λ˜μ—ˆμŠ΅λ‹ˆλ‹€!

  • onMouseDown: 마우슀λ₯Ό ν΄λ¦­ν–ˆμ„ λ•Œ μ‹œμž‘ μ’Œν‘œλ₯Ό μ €μž₯.
  • onMouseMove: 마우슀λ₯Ό 움직이며 μ‹œμž‘ μ’Œν‘œμ™€μ˜ 차이λ₯Ό 계산해 지도λ₯Ό 이동.
  • onWheel: 마우슀 휠 λ™μž‘μœΌλ‘œ ν™•λŒ€/μΆ•μ†Œλ₯Ό κ΅¬ν˜„.

마우슀 μ΄λ²€νŠΈλŠ” 1개의 ν¬μΈν„°λ§Œ 닀루면 되고, 마우슀의 이동, 클릭, 휠 μ΄λ²€νŠΈκ°€ λͺ…ν™•νžˆ κ΅¬λΆ„λ˜κΈ° λ•Œλ¬Έμ— μƒλŒ€μ μœΌλ‘œ κ°„λ‹¨ν–ˆμŠ΅λ‹ˆλ‹€.


πŸ“± ν„°μΉ˜ 이벀트 μΆ”κ°€μ˜ μ£Όμš” κ³ λ―Ό

마우슀 μ΄λ²€νŠΈμ™€ λ‹€λ₯΄κ²Œ ν„°μΉ˜ 이벀트λ₯Ό κ΅¬ν˜„ν•˜λ €λ©΄ λ§ˆμš°μŠ€μ™€ λ‹€λ₯Έ 접근이 ν•„μš”ν–ˆκ³ , 마우슀 μ΄λ²€νŠΈμ™€μ˜ 차이λ₯Ό μž‘μ„±ν•΄λ³΄μžλ©΄ μ•„λž˜μ™€ 같이 정리할 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

1. ν„°μΉ˜ μ΄λ²€νŠΈλŠ” μ—¬λŸ¬ 개의 포인터λ₯Ό 닀룬닀

  • 마우슀 μ΄λ²€νŠΈλŠ” 포인터가 ν•˜λ‚˜λ§Œ μ‘΄μž¬ν•˜μ§€λ§Œ, ν„°μΉ˜ μ΄λ²€νŠΈλŠ” μ—¬λŸ¬ 손가락(포인터)을 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • ν•œ μ†κ°€λ½μœΌλ‘œλŠ” 이동을 κ΅¬ν˜„ν•˜κ³ , 두 μ†κ°€λ½μœΌλ‘œλŠ” ν™•λŒ€/μΆ•μ†Œλ₯Ό κ΅¬ν˜„ν•΄μ•Ό ν–ˆμŠ΅λ‹ˆλ‹€β€¦β€¦β€¦.ν•˜ν•˜β€¦..
  • 이 κ³Όμ •μ—μ„œ ν„°μΉ˜ 개수(e.touches.length)에 따라 λ‘œμ§μ„ λΆ„κΈ° μ²˜λ¦¬ν•΄μ•Ό ν–ˆμŠ΅λ‹ˆλ‹€.

2. ν„°μΉ˜ λ™μž‘μ˜ μ˜λ„ νŒŒμ•…

  • ν•œ μ†κ°€λ½μœΌλ‘œ ν„°μΉ˜ν–ˆμ„ λ•Œ, μ΄λŠ” λ‹¨μˆœνžˆ 클릭인지 μ•„λ‹ˆλ©΄ 이동을 μœ„ν•œ ν„°μΉ˜μΈμ§€λ₯Ό ꡬ뢄해야 ν–ˆμŠ΅λ‹ˆλ‹€.
  • 두 μ†κ°€λ½μœΌλ‘œ ν„°μΉ˜ν–ˆμ„ λ•Œ, 이 λ™μž‘μ΄ ν™•λŒ€/μΆ•μ†Œλ₯Ό μœ„ν•œ λ™μž‘μž„μ„ λͺ…ν™•νžˆ 인식해야 ν–ˆμŠ΅λ‹ˆλ‹€.

3. 쀌 λΉ„μœ¨ 계산

  • 두 손가락 ν„°μΉ˜λ‘œ ν™•λŒ€/μΆ•μ†Œλ₯Ό κ΅¬ν˜„ν•˜λ €λ©΄ 두 손가락 κ°„μ˜ 초기 거리와 이동 쀑 거리λ₯Ό λΉ„κ΅ν•˜μ—¬ 쀌 λΉ„μœ¨μ„ 계산해야 ν–ˆμŠ΅λ‹ˆλ‹€.
  • λ‹¨μˆœνžˆ λΉ„μœ¨μ„ κ³„μ‚°ν•˜λŠ” κ²ƒλΏλ§Œ μ•„λ‹ˆλΌ, 쀌의 쀑심을 μ •ν™•νžˆ μ„€μ •ν•΄μ•Ό μžμ—°μŠ€λŸ¬μš΄ ν™•λŒ€/μΆ•μ†Œκ°€ κ°€λŠ₯ν–ˆμŠ΅λ‹ˆλ‹€.

πŸ›  ν„°μΉ˜ 이벀트 κ΅¬ν˜„ κ³Όμ •

κ·Έλž˜μ„œ μ €λŠ” ν„°μΉ˜ 이벀트λ₯Ό μΆ”κ°€ν•˜κΈ° μœ„ν•΄ μ•„λž˜μ™€ 같은 과정을 κ±°μ³€μŠ΅λ‹ˆλ‹€.

1. κΈ°λ³Έ 이벀트 μΆ”κ°€

ν„°μΉ˜ λ™μž‘μ€ onTouchStart, onTouchMove, onTouchEnd 이벀트둜 μ²˜λ¦¬ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό 톡해 ν„°μΉ˜ μ‹œμž‘, μ›€μ§μž„, μ’…λ£Œ μ‹œ ν•„μš”ν•œ μƒνƒœλ₯Ό μ—…λ°μ΄νŠΈν•˜λ„λ‘ κΈ°λ³Έ ꡬ쑰λ₯Ό μž‘μ„±ν–ˆμŠ΅λ‹ˆλ‹€.

κ·Έλž˜λ„ λ§ˆμš°μŠ€λ³΄λ‹€ 닀행인 것은, mouseMove μ΄λ²€νŠΈμ—μ„œλŠ” ν΄λ¦­ν•œ μƒνƒœλ‘œ μ›€μ§μ΄λŠ” 것인지, κ·Έλƒ₯ ν΄λ¦­λ§Œν•˜κ³  λλ‚˜λŠ” 것인지 등을 λΆ„λ₯˜ν•΄μ£ΌλŠ” μž‘μ—…μ΄ μ–΄λ €μ› λŠ”λ°,

ν„°μΉ˜ μ΄λ²€νŠΈλŠ” μ• μ΄ˆμ— ν„°μΉ˜λ₯Ό ν•œ μƒνƒœλ‘œ μ›€μ§μ΄λŠ” μ΄λ²€νŠΈκ°€ touchMove둜 μ‘΄μž¬ν–ˆκΈ° λ•Œλ¬Έμ—, ν„°μΉ˜λœ μƒνƒœλ‘œ 움직이고 μžˆλŠ” 건지λ₯Ό λΆ„λ¦¬ν•˜λŠ” 둜직이 ν•„μš”μ—†λ‹€λŠ” μž₯점이 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

(개인적으둜 마우슀 이동할 λ•Œ 이 둜직 μ§œλŠ”κ²Œ κ°€μž₯ μ–΄λ ΅κ³  정닡이 μ—†μ—ˆλŠ”λ°, ν„°μΉ˜λŠ” κ·ΈλŸ¬μ§€ μ•Šμ•„λ„ λ˜μ–΄μ„œ λ„ˆλ¬΄ μ’‹μ•˜μŠ΅λ‹ˆλ‹€β€¦..γ…Žγ…Žγ…Ž,,,)

const handleTouchStart = (e: React.TouchEvent) => {
  if (e.touches.length === 2) {
    // 두 손가락 ν„°μΉ˜ μ‹œμž‘
  } else if (e.touches.length === 1) {
    // ν•œ 손가락 ν„°μΉ˜ μ‹œμž‘
  }
};

const handleTouchMove = (e: React.TouchEvent) => {
  if (e.touches.length === 2) {
    // 두 μ†κ°€λ½μœΌλ‘œ ν™•λŒ€/μΆ•μ†Œ
  } else if (e.touches.length === 1) {
    // ν•œ μ†κ°€λ½μœΌλ‘œ 지도 이동
  }
};

const handleTouchEnd = (e: React.TouchEvent) => {
  // ν„°μΉ˜ μ’…λ£Œ μ‹œ μƒνƒœ μ΄ˆκΈ°ν™”
};

2. ν•œ 손가락 ν„°μΉ˜λ‘œ 지도 이동

문제:

  • ν•œ μ†κ°€λ½μœΌλ‘œ ν„°μΉ˜ν•œ μƒνƒœμ—μ„œ μ΄λ™ν•˜λ €λ©΄ ν˜„μž¬ ν„°μΉ˜ μ’Œν‘œμ™€ 이전 ν„°μΉ˜ μ’Œν‘œ κ°„μ˜ 차이λ₯Ό 계산해야 ν–ˆμŠ΅λ‹ˆλ‹€.
  • ν•˜μ§€λ§Œ ν„°μΉ˜ν•œ μœ„μΉ˜λ₯Ό λ‹¨μˆœνžˆ 클릭으둜 μ²˜λ¦¬ν•  κ°€λŠ₯성도 μžˆμ—ˆκΈ° λ•Œλ¬Έμ—, 이동인지 클릭인지λ₯Ό ꡬ뢄할 ν•„μš”κ°€ μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

ν•΄κ²° 방법:

  • ν„°μΉ˜ μ‹œμž‘ μ‹œ(onTouchStart) μœ„μΉ˜λ₯Ό μ €μž₯ν•œ λ’€, ν„°μΉ˜κ°€ μ›€μ§μ΄λŠ” λ™μ•ˆ(onTouchMove)의 μ’Œν‘œμ™€ 비ꡐ해 이동 거리λ₯Ό κ³„μ‚°ν–ˆμŠ΅λ‹ˆλ‹€.
  • 이동 거리가 일정 κ°’ 이상이면 클릭이 μ•„λ‹ˆλΌ μ΄λ™μœΌλ‘œ κ°„μ£Όν–ˆμŠ΅λ‹ˆλ‹€.
const handleTouchStart = (e: React.TouchEvent) => {
  if (e.touches.length === 1) {
    const rect = canvasRef.current?.getBoundingClientRect();
    if (!rect) return;

    setDragStartPos({
      x: e.touches[0].clientX - rect.left,
      y: e.touches[0].clientY - rect.top,
    });
    setIsTouching(true);
  }
};

const handleTouchMove = (e: React.TouchEvent) => {
  if (isTouching && e.touches.length === 1) {
    const rect = canvasRef.current?.getBoundingClientRect();
    if (!rect) return;

    const newX = e.touches[0].clientX - rect.left;
    const newY = e.touches[0].clientY - rect.top;

    const deltaX = dragStartPos.x - newX;
    const deltaY = dragStartPos.y - newY;

    map?.panBy(new naver.maps.Point(deltaX, deltaY));
    setDragStartPos({ x: newX, y: newY });
  }
};

3. 두 손가락 ν„°μΉ˜λ‘œ ν™•λŒ€/μΆ•μ†Œ

문제:

  • 두 μ†κ°€λ½μ˜ ν„°μΉ˜ κ°„ 거리λ₯Ό κΈ°μ€€μœΌλ‘œ 쀌 λΉ„μœ¨μ„ 계산해야 ν–ˆμŠ΅λ‹ˆλ‹€.
  • ν™•λŒ€/μΆ•μ†Œμ˜ 쀑심점은 두 μ†κ°€λ½μ˜ 쀑심이어야 ν•˜λ―€λ‘œ, 두 μ†κ°€λ½μ˜ 쀑심 μ’Œν‘œλ₯Ό 지도 μ’Œν‘œλ‘œ λ³€ν™˜ν•΄ 쀌 μ€‘μ‹¬μœΌλ‘œ μ„€μ •ν•΄μ•Ό ν–ˆμŠ΅λ‹ˆλ‹€.

ν•΄κ²° 방법:

  1. 두 손가락 κ°„ 거리λ₯Ό κ³„μ‚°ν•˜μ—¬ μ‹œμž‘ 거리와 ν˜„μž¬ 거리의 λΉ„μœ¨λ‘œ 쀌 크기λ₯Ό 계산.
  2. 두 μ†κ°€λ½μ˜ 쀑심 μ’Œν‘œλ₯Ό 지도 μ’Œν‘œλ‘œ λ³€ν™˜ν•΄ 쀌 쀑심(zoomOrigin)으둜 μ„€μ •.
  3. 쀌 크기 변경에 따라 지도 μƒμ˜ ν™•λŒ€/μΆ•μ†Œλ₯Ό 적용.
const handleTouchStart = (e: React.TouchEvent) => {
  if (e.touches.length === 2) {
    const distance = Math.sqrt(
      Math.pow(e.touches[0].clientX - e.touches[1].clientX, 2) +
        Math.pow(e.touches[0].clientY - e.touches[1].clientY, 2),
    );

    setTouchStartDistance(distance);

    const centerX = (e.touches[0].clientX + e.touches[1].clientX) / 2;
    const centerY = (e.touches[0].clientY + e.touches[1].clientY) / 2;

    const mapCenter = map?.getProjection().fromContainerPixelToLatLng(
      new naver.maps.Point(centerX, centerY),
    );

    setTouchCenter(mapCenter);
  }
};

const handleTouchMove = (e: React.TouchEvent) => {
  if (e.touches.length === 2 && touchStartDistance) {
    const newDistance = Math.sqrt(
      Math.pow(e.touches[0].clientX - e.touches[1].clientX, 2) +
        Math.pow(e.touches[0].clientY - e.touches[1].clientY, 2),
    );

    const zoomChange = (newDistance - touchStartDistance) / 30; // λΉ„μœ¨ μ‘°μ •
    const currentZoom = map?.getZoom() ?? 10;

    map?.setOptions({ zoomOrigin: touchCenter });
    map?.setZoom(currentZoom + zoomChange);

    setTouchStartDistance(newDistance);
  }
};

🧩 κ΅¬ν˜„ κ³Όμ • 쀑 λ°œμƒν•œ λ¬Έμ œμ™€ ν•΄κ²°

1. 쀌 λΉ„μœ¨μ˜ κ³Όλ„ν•œ λ³€ν™”

쀌 λΉ„μœ¨ κ³„μ‚°μ—μ„œ 거리 차이에 따라 쀌 λ³€ν™”λŸ‰μ„ 직접 λ°˜μ˜ν–ˆλ”λ‹ˆ ν™•λŒ€/μΆ•μ†Œκ°€ λ„ˆλ¬΄ λ―Όκ°ν•˜κ²Œ λ°˜μ‘ν–ˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ μŠ€μΌ€μΌλ§ λΉ„μœ¨μ„ μ‘°μ •ν•˜μ—¬ 쀌 λ³€ν™”λŸ‰μ„ 적절히 μ œν•œν•΄μ£Όκ³  μžˆμŠ΅λ‹ˆλ‹€.

λ‹€λ§Œ,,,, κ·Έ μž„κ³„κ°’μ΄ μ™„λ²½ν•˜μ§€ μ•ŠκΈ°μ— 아직도 κ°œμ„ ν•΄μ•Όν•˜λŠ” λΆ€λΆ„ 쀑 ν•˜λ‚˜μž…λ‹ˆλ‹€β€¦β€¦γ… γ… γ… 


2. 쀌 쀑심이 ν™”λ©΄ λ°–μœΌλ‘œ λ²—μ–΄λ‚˜λŠ” 문제

두 μ†κ°€λ½μ˜ 쀑심을 κΈ°μ€€μœΌλ‘œ μ€Œμ„ μ μš©ν–ˆμ§€λ§Œ, 쀑심 μ’Œν‘œκ°€ 잘λͺ» κ³„μ‚°λ˜μ–΄ ν™”λ©΄ λ°–μœΌλ‘œ λ²—μ–΄λ‚˜λŠ” λ¬Έμ œκ°€ μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

이λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ 지도 μ’Œν‘œ λ³€ν™˜(fromContainerPixelToLatLng)을 톡해 μ •ν™•νžˆ 쀑심을 μ„€μ •ν•΄μ£Όμ—ˆμŠ΅λ‹ˆλ‹€.


πŸ“ κ²°λ‘ 

ν„°μΉ˜ μ΄λ²€νŠΈλŠ” 마우슀 μ΄λ²€νŠΈμ™€ κΈ°λ³Έ 원리가 λΉ„μŠ·ν•˜μ§€λ§Œ, λ©€ν‹° 포인터λ₯Ό μ²˜λ¦¬ν•΄μ•Ό ν•œλ‹€λŠ” 점과 ν„°μΉ˜ λ™μž‘μ˜ μ˜λ„λ₯Ό νŒŒμ•…ν•΄μ•Ό ν•œλ‹€λŠ” μ μ—μ„œ 좔가적인 고민이 ν•„μš”ν–ˆμŠ΅λ‹ˆλ‹€.

특히 두 손가락 쀌의 쀑심을 μ •ν™•νžˆ κ³„μ‚°ν•˜κ³ , λ™μž‘μ˜ μžμ—°μŠ€λŸ¬μ›€μ„ μœ μ§€ν•˜λŠ” 것이 κ°€μž₯ 큰 도전 κ³Όμ œμ˜€μŠ΅λ‹ˆλ‹€.

λ§ˆμš°μŠ€μ—μ„œλŠ” ν΄λ¦­ν•œ μƒνƒœλ‘œ μ΄λ™ν•˜λŠ” 것을 μΆ”μ ν•˜λŠ”κ²Œ 쉽지 μ•Šμ•˜λ‹€λ©΄, 였히렀 ν„°μΉ˜μ΄λ²€νŠΈλŠ” 이동이 μ•„λ‹ˆλΌ μ€Œμ„ μ–Όλ§ˆλ‚˜ 할지에 λŒ€ν•΄ μ •ν•˜λŠ” 것이 쉽지 μ•Šμ•˜λ˜ 것 κ°™μŠ΅λ‹ˆλ‹€.

ν•˜μ§€λ§Œ 결둠적으둜 μ•„λž˜μ™€ 같이 κ΅¬ν˜„ν–ˆκ³ , ν„°μΉ˜ μ΄λ²€νŠΈκ°€ 잘 λ™μž‘ν•˜λŠ” 것도 확인할 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€ γ…Žγ…Ž
(λ§Œμ•½ 이미지가 λ©ˆμΆ°μžˆλ‹€λ©΄, μ΄λ―Έμ§€μ—μ„œ 였λ₯Έμͺ½ 마우슀 클릭 ν›„ μƒˆ νƒ­μ—μ„œ 이미지 μ—΄κΈ°λ‘œ λ“€μ–΄κ°€μ£Όμ„Έμš”... λ²¨λ‘œκ·Έκ°€ gifλ₯Ό μ§€μ›ν•˜μ§€ μ•ŠλŠ”κ°€λ΄μš”....)

(이 λ•Œ μ§„μ§œ λ„ˆλ¬΄ λΏŒλ“―ν•΄μ„œ λ‚ μ•„κ°€λŠ” 쀄 μ•Œμ•˜μ–΄μš”;; 또 λ°œμƒν•  λ¬Έμ œλŠ” λͺ¨λ₯Έμ±„…)

0개의 λŒ“κΈ€