[나만무/개발] Web에서 모바일 Pinch Zoom 구현

CHO WanGi·2025년 7월 28일

KRAFTON JUNGLE 8th

목록 보기
89/89

https://tech.kakaoent.com/front-end/2023/230310-webview-pinch-zoom/
다시한번 압도적 감사...

Pinch Zoom?

우리가 지도앱을 쓸때 많이 하는 행위로,
손가락 두개가 멀어지거나 가까워지는 제스처를 Pinch Zoom 이라고 한다.

코드로는 어떻게 감지할까?

  const handleTouchMove = useCallback(
    (e: React.TouchEvent<HTMLCanvasElement>) => {
      e.preventDefault();
      const touches = e.touches;
      const rect = interactionCanvasRef.current!.getBoundingClientRect();

      if (touches.length === 2) {
        const dx = touches[0].clientX - touches[1].clientX;
        const dy = touches[0].clientY - touches[1].clientY;
        const newDistance = Math.sqrt(dx * dx + dy * dy);
        const oldDistance = pinchDistanceRef.current;

        if (oldDistance > 0) {
          const scaleFactor = newDistance / oldDistance;
          const newScale = Math.max(
            MIN_SCALE,
            Math.min(MAX_SCALE, scaleRef.current * scaleFactor)
          );
          const centerX =
            (touches[0].clientX + touches[1].clientX) / 2 - rect.left;
          const centerY =
            (touches[0].clientY + touches[1].clientY) / 2 - rect.top;

          const xs = (centerX - viewPosRef.current.x) / scaleRef.current;
          const ys = (centerY - viewPosRef.current.y) / scaleRef.current;

          viewPosRef.current.x = centerX - xs * newScale;
          viewPosRef.current.y = centerY - ys * newScale;
          scaleRef.current = newScale;

          draw();
          updateOverlay(centerX, centerY);
        }
        pinchDistanceRef.current = newDistance;
        return;
      }
  • 두 손가락 제스처 감지

touches.length === 2, 화면에 닿은 손가락이 2개일 때만 확대/축소 로직을 실행한다.

  • 거리 및 Scale 계산

dx, dy로 두 손가락의 화면상의 x 좌표와, y좌표를 계산한다.
첫 두손가락의 거리 사이가 oldDistance에 저장된다.

사용자가 Pinch Zoom 을 수행하면 두 손가락 사이의 거리가 바뀐다.
이때 피타고라스 정리를 활용하여 계산한 결과가 newDistance.
const newDistance = Math.sqrt(dx * dx + dy * dy);

이 두 값을 비교하여 확대/축소 배율인 scaleFactor를 계산하고,
기존 배율인 scaleRef.current에 이를 곱하여 확대/축소를 진행한다.

중심점 기준 Panning.

이때 확대와 축소는 두 손가락의 중심점을 기준으로 진행하게 된다.
이렇게 안하면 사용자가 좌측 상단에 중심점을 두든,
우측 하단에 중심점을 두든 중앙에서 확대/축소가 된다.

  • 중간점 계산
    centerX, centerY로 두 손가락의 중간 지점을 계산한다.
    viewPosRef, scaleRef 를 고려하여, 저 두 값이 실제 캔버스의 어느 좌표에 있는지 계산
const xs = (centerX - viewPosRef.current.x) / scaleRef.current;
const ys = (centerY - viewPosRef.current.y) / scaleRef.current;

이 좌표가 다시 화면의 CenterX, CenterY 값으로 적용되어,
사용자가 손가락으로 가리키는 지점을 중심으로 확대/축소되는 동작을 구현하였다.

🚨viewport meta가 Safari에선 안된다고?

    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"
    />

구글링했을때, 분명히 "initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no" 하면 브라우저 기본 확대 동작을 막을 수 있다고 했다.

문제상황

iOS 버전이 업데이트 되면서 safari에서는
viewport를 통해 브라우저를 확대하지 못하게 하는 방법이 불가능하게 되었다.
그래서 중간 발표때 모바일에서 도저히 프로덕트가 동작하지 않게 되었다.

해결책

처음엔 touch 이벤트에 preventDefault를 달아서 막아보고자 했는데,
최상위 파일인 Main.tsx에 아래와같이 추가하였더니 기본동작을 막을 수 있었다.

document.addEventListener(
  'touchmove',
  (event) => {
    event.preventDefault();
  },
  { passive: false }
);
profile
제 Velog에 오신 모든 분들이 작더라도 인사이트를 얻어가셨으면 좋겠습니다 :)

0개의 댓글