롱프레스 이벤트를 사용하자

JeongHoon Park·2022년 4월 8일
1
post-thumbnail

롱프레스 이벤트 리스너란?

롱프레스 이벤트란 말이 익숙치 않은 사람들이 많을 것이다. 하지만 설명을 듣는 순간 모두들 아 그거~라고 할 것이다. 롱프레스 이벤트란 터치 홀드라고도 불린다. 한 요소를 길게 누르고 있는 이벤트다. 우리가 보통 핸드폰에서 앱을 지우기위해 메뉴를 띄우거나 이동하게 하기 위해서 길게 누르는 그 액션을 롱프레스라고 한다. 스마트폰이 익숙한 우리에게는 이 이벤트가 익숙하지 않을 수 없다.

보통 롱프레스 이벤트는 핸드폰의 앱아이콘들처럼 단순 터치를 할 때 이미 다른 기능이 있거나 혹은 자주 터치가 일어나는 요소에 새로운 이벤트 리스너를 넣고 싶을 때 많이 사용된다. 밑미 프로젝트를 할 때 우리는 댓글을 삭제/수정하거나 신고를 하기위한 모달을 띄워야했다. 모바일 사용을 위한 웹앱 구현이였기 때문에 댓글 요소에 많은 터치가 생길 수 밖에 없었기에 롱프레스 이벤트가 일어나면 모달을 띄우기로 결정했다.


구현 방법

우리는 롱프레스의 기준을 3초로 정했다. 3초동안 댓글을 누르면 해당 댓글에 대한 모달이 뜨게하고 만약 그전에 손을 땐다면 모달이 뜨지 않도록 구현했다.

모달이 뜨게하기 위해서 onTouchStart가 일어난다면 setTimeout API를 활용하여 3초 이후에 함수가 실행되도록 예약을 했다. 그리고 모달이 뜨지 않도록하기 위해서는 onTouchEnd가 일어났을 때 clearTimeout API를 사용하였다. 이러한 과정에서 해당 타겟 요소와 timeout API를 기억하기 위해서 useRef를 활용하였다.

위의 코드는 롱프레스 이벤트 리스너 기능을 커스텀 훅으로 만든 결과물이다.

// useLongPress.ts
import React, { useCallback, useRef } from 'react';

const useLongPress = (
  onLongPress: () => void,
  { delay = 300 } = {},
) => {
  const timeout = useRef<NodeJS.Timeout>();
  const target = useRef<HTMLElement>();

  const start = useCallback(
    (event) => {
      if (event.target) {
        event.target.addEventListener('touchend', preventDefault, {
          passive: false,
        });
        target.current = event.target;
      }
      timeout.current = setTimeout(() => {
        onLongPress();
      }, delay);
    },
    [onLongPress],
  );

  const clear = useCallback(
    () => {
      if (timeout.current) clearTimeout(timeout.current);
      if (target.current) {
        target.current.removeEventListener('touchend', preventDefault);
      }
    },
    [],
  );

  return {
    onTouchStart: (e: React.TouchEvent) => start(e),
    onTouchEnd: () => clear(),
  };
};

const isTouchEvent = (event: TouchEvent) => 'touches' in event;

const preventDefault = (event: TouchEvent) => {
  if (!isTouchEvent(event)) return;

  if (event.touches.length < 2 && event.preventDefault) {
    event.preventDefault();
  }
};

// targetComponent.tsx
...
const { onMouseDown, onMouseLeave, onMouseUp, onTouchEnd, onTouchStart } =
    useLongPress(() => funtion());
...
<div
  onTouchEnd={onTouchEnd}
  onTouchStart={(e) => onTouchStart(e)}
>
  타겟 요소
</div>

정리

단순히 기능만 보면 간단하게 구현이 될 것이라고 생각했지만 생각보다 복잡했었다. 해당 글에서 다루지는 않았지만 여러 댓글 중에 어떤 댓글을 위한 모달인지 확인을 하기위해서 댓글 데이터를 따로 저장하는 기능을 추가 구현하여 원하던 기능을 완성하였다. 많은 생각을 하고 많은 에러를 만나면서 구현했던 기능 중 하나였고 매우 유용하게 사용하였다.

profile
Develop myself, FE developer!

0개의 댓글