pile·
프론트엔드·카카오 엔터테인먼트 FE카카오 엔터테인먼트 FE·

Webview에서 핀치 줌(pinch zoom) 구현하기

모바일 웹에서 핀치 줌을 라이브러리 없이 직접 구현하는 과정을 다룬다. 자바스크립트는 화면 어느 지점에 터치가 발생했다는 정보만 줄 뿐 어떤 제스처인지 판단해주지 않으므로, 두 터치 지점 사이의 거리 변화를 계산해 확대와 축소를 직접 판정해야 한다. 브라우저 기본 줌을 막는 것부터 확대 중심 보정까지 최소 구현으로 따라간다.

핵심 포인트
  • 직접 구현하는 이유는 쓰던 제스처 라이브러리가 deprecated 되어 유지보수가 끊긴 경험 때문이다. 간단한 로직은 직접 갖고 있어야 이슈나 기획 변경에 즉시 대응할 수 있다.
  • 개발과 디버깅은 크롬 원격 디버거로 한다. 유선 연결한 실기기를 chrome://inspect/#devices 에서 inspect 해 모바일 화면을 크롬으로 송출한다. iOS 는 사파리 web inspector 를 쓴다.
  • 터치 정보는 changedTouches 에서 가져온다. touches 와 targetTouches 는 변하지 않은 정적 터치까지 포함해 핀치 줌에는 적합하지 않다.
  • iOS 버전이 올라가며 viewport meta 의 user-scalable 로 브라우저 확대를 막는 방법이 통하지 않게 됐다. touches 개수가 1 을 넘을 때 preventDefault 하거나 CSS touch-action 을 none 으로 두는 우회가 필요하다.
  • 핀치 판정은 두 터치 지점의 유클리드 거리를 touchmove 마다 계산해 이전 거리와 비교하는 방식이다. 멀어지면 zoom in, 좁아지면 zoom out 이다.
상세 정리
  • 사용하는 이벤트는 touchstart, touchmove, touchend, touchcancel 네 가지다.
  • touch 객체에서 실제로 쓰는 값은 identifier 와 clientX, clientY 세 개뿐이다.
  • changedTouches 는 유사 배열 객체이고 멀티 터치라 여러 개가 담길 수 있다. 이걸 고려하지 않고 처리하면 오작동한다.
  • 기본 줌을 막는 리스너는 passive 를 false 로 등록해야 preventDefault 가 먹는다. 다만 공식적으로 권장되는 UX 가 아니고 성능과 사이드 이펙트를 따져야 한다고 경고한다.
  • 구조는 터치를 받는 screen 영역과 실제로 확대되는 target 영역을 분리한다. 타겟에 이벤트를 걸면 작아진 타겟을 다시 확대하기 어려워지기 때문이다.
  • 타겟의 기본 크기는 scale 이 1 일 때 스크린과 같게 두는 편이 계산에 편하다. 그래서 이미지 위에 wrapper 를 하나 더 뒀다.
  • 엘리먼트를 핀치 줌에 바로 넘기지 않고 touchInit 으로 한 번 감싼다. 나중에 드래그 이벤트를 추가할 때 같은 상태 값을 공유하기 위해서다.
  • 상태 갱신은 setState 가 target 의 transform 에 scale 을 써넣는 방식이고, 확대 폭은 핀치가 발생할 때마다 0.02 씩 더하거나 뺀다. 정밀하게 하려면 줌 발생량에 비례시키면 된다고 덧붙인다.
  • 추적 자료구조는 이전 거리를 담는 prevDiff 와 진행 중인 터치 객체 배열 evHistory 두 개다.
  • touchstart 는 배열 길이가 2 를 넘지 않는 선에서만 push 하고, touchend 와 touchcancel 은 identifier 로 찾아 splice 한다. 진행 중인 멀티 터치에 다른 터치가 끼어들지 못하게 막는 장치다.
  • touchmove 에서는 identifier 로 추적 대상인지 먼저 확인하고 위치를 갱신한 뒤, evHistory 길이가 정확히 2 일 때만 x 와 y 차이로 거리를 구한다.
  • 첫 핀치는 비교 대상이 없으므로 prevDiff 가 -1 인 동안에는 콜백 호출을 건너뛴다.
  • 개선판에서는 확대 중심 보정을 넣는다. 두 손가락의 중심점이 확대 방향으로 멀어지므로 그 차이를 계산해 x, y 를 함께 옮기고, transform 에 translateX 와 translateY 를 scale 과 같이 적용한다.
왜 읽나모바일 웹뷰에서 제스처를 라이브러리 없이 다뤄야 하는 프론트엔드 개발자에게 터치 이벤트 추적 구조와 브라우저 기본 줌 차단 방법을 짚어주는 구현 가이드.
카카오 엔터테인먼트 FE
카카오 엔터테인먼트 FE 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

  1. 프론트엔드·여기어때 (GC컴퍼니)여기어때 (GC컴퍼니)·

    항공 프론트엔드 구축기 (7/10): 창구를 하나만 두었습니다

    여기어때 항공 서비스 프론트엔드가 웹과 앱 웹뷰 두 환경에서 동일한 함수 호출로 동작하는 앱 브릿지 추상화 레이어를 설계한 과정을 다룬다. iOS·안드로이드 규약 차이와 "웹에 존재하지 않는 브릿지를 어떻게 호출하나"라는 문제를 단일 추상화 층으로 해결한 구현 사례다.

    요약 이어보기
    #react#typescript#webview+2