본문 바로가기

프론트엔드

Hammer.js로 swipe 이벤트 감지하기

  • 프로젝트: 채식 지도
  • 키워드: Hammer.js, swipe event, mobile swipe gesture, touch-action
  • 상황
    1. 뒤로가기, Sheet 내리기, Sheet 올리기 동작은 버튼을 눌러 실행할 수 있다.
      (뒤로가기는 '<' 버튼, Sheet 내리기는 'X' 버튼, Sheet 올리기는 '^' 버튼)
      뒤로가기, Sheet 내림 버튼
      Sheet 올림 버튼
    2. 모바일 기기에서 버튼을 하나하나 누르는 것은 불편하기 때문에, 뒤로가기(swipe right), Sheet 내리기(swipe bottom), Sheet 올리기(swipe top)를 swipe 이벤트를 통해 실행하고자 한다.
  • 해결 방법
    1. js로 직접 구현할 수 있겠지만, 다양한 브라우저를 대응하고 threshold를 일일이 구현하는 것보다는 간단하게 Hammer.js 라이브러리를 사용하기로 한다.

        yarn add hammerjs
      yarn add -D @types/hammerjs


    2. 이벤트를 추가하고자 하는 element를 Hammer 생성자에 넘기고, on API를 이용하여 이벤트를 연결한다.

        // ListSection.tsx
      
      // Sheet가 펼쳐졌는지 나타내는 상태
      const [isListExpanded, setIsListExpanded] = useState(false);
      const toggleListExpanded = () => {
        setIsListExpanded((expanded) => !expanded);
      };
      
      ...
      
      useEffect(() => {
        let hammer: HammerManager;
      
        // DOM 렌더링이 끝난 후 동적 import
        import('hammerjs').then(() => {
          const listSection = document.querySelector<HTMLElement>('.list-section');
          if (!listSection) return;
      
          hammer = new Hammer(listSection);
          // 수직, 수평 방향 모두 이용할 것이므로 DIRECTION_ALL 사용
          hammer.get('swipe').set({
            direction: Hammer.DIRECTION_ALL,
          });
      
          // Sheet 올리기
          hammer.on('swipeup', () => {
            setIsListExpanded(true);
          });
      
          // Sheet 내리기
          hammer.on('swipedown', () => {
            setIsListExpanded(false);
          });
          
          // 뒤로가기
          hammer.on('swiperight', () => {
            뒤로가기함수();
          });
        });
      
        return () => {
          hammer.destroy();
        };
      }, []);
      
      return (
          <ListSection isListExpanded={isListExpanded} className="list-section">
            ...Sheet 안의 내용...
          </ListSection>
      );


    3. 이제 ListSection(Sheet UI)에 대해 swipe 이벤트가 감지된다.
      다만 문제인 것은 ListSection 내부에 스크롤 영역이 있는 경우이다. 스크롤 영역은 touch action이 발생할 수 있는 영역이기 때문에(swipedown하여 스크롤을 내리는 행위 등) Hammer.js의 swipe이벤트가 작동하지 않는다.

      예를 들어, Sheet가 올라와 있는 현재 상황에서 스크롤 영역(매장 목록 리스트, StoreList)을 swiperight(뒤로 가기)해도 Hammer.js가 작동하지 않는다.
      swipeup, swipedown은 스크롤을 우선적으로 해야하기 때문에 Hammer의 이벤트(Sheet 올리기/내리기)가 작동하지 않는 것이 맞지만, swiperight 이벤트까지 막힐 이유는 없다.
      스크롤 영역인 매장 목록(StoreList)
    4. 이 버그는 매장 목록(StoreList)의 style에 touch-action CSS를 추가하여 해결할 수 있다.
        touch-action: pan-y;
      이제 StoreList에서 수직(y) 방향의 스크롤은 의도대로 동작하고, 수평(x) 방향의 swiperight는 부모 element인 ListSection의 Hammer 이벤트(뒤로 가기)가 작동한다.

    5. 의도대로 swipe 이벤트가 작동하는 것을 확인할 수 있다!