본문 바로가기
source-code/React

react에서 long press 구현하기

by mattew4483 2023. 8. 16.
728x90
반응형

웹에서 long press를 구현할 일이 생겼다.

원했던 기능은 타임 테이블에서 꾹 누르기 + 드래그를 통해 예약하고자 하는 날짜를 지정하는 것!

 

PC에서는 drag만 있어도 해당 기능을 구현할 수 있지만, 

tablet에서는 스크롤 이벤트와 drag 기능이 중첩되어 의도한 동작이 이뤄지지 않기 때문에...

long press를 추가해 이를 해결하고자 했다.

 

기본적인 long press의 개념은

1. 사용자가 화면을 누른 상태에서, 지정 시간이 지난 후에야, 특정 동작이 수행된다.

2. 지정 시간이 흐르기 전에 사용자가 화면에서 손을 떼면, 특정 동작이 수행되지 않는다.

 

우선, 화면을 눌렀을 때/손을 땠을 때는 pointer event를 통해 감지할 수 있다!

https://ko.javascript.info/pointer-events

 

Pointer events

 

ko.javascript.info

pointer event를 통해 마우스, 펜, 터치 스크린 등 여러 포인팅 장치 입력을 한 번에 처리할 수 있다!

(이전에는... pc = mouse event / tablet = touch event로 처리해야했는데...

정확히는 "ontouchstart" in document.documentElement)처럼 document 객체에 touch event의 존재 여부를 통해, 핸들링할 이벤트를 지정해야 했다..! )

 

그렇다면..!

1. onPointerDown 이벤트 발생 시, start touch 상태 변경

2. long press 상태 변경 시, 지정 시간 이후 callback으로 받은 함수 실행(setTimeout)

3. onPointerUp 이벤트 발생 시, start touch 상태 변경

4. 지정 시간 내에 start touch 상태가 변경되었을 경우 => callback으로 받은 함수 실행 timeout 제거

정도로 생각하면 될 테다!

 

const useLongPress = (callback: () => void, ms = 500) => {
  const [startLongPress, setStartLongPress] = useState(false)

  useEffect(() => {
    let longPressTimer: NodeJS.Timeout
    if (startLongPress) {
      longPressTimer = setTimeout(() => {
        callback()
      }, ms)
    }
    return () => {
      clearTimeout(longPressTimer)
    }
  }, [startLongPress])

  return {
    onPointerDown: () => setStartLongPress(true),
    onPointerUp: () => setStartLongPress(false),
  }
}

export default useLongPress

처음 작성한 hooks.

start long press를 boolean 형태의 state로 관리했고,

해당 상태에 의존하는 effect를 통해 longPressTimer라는 Timeout을 관리했다.

 

즉 pointer 이벤트가 발생할 때마다, startLongPress 상태가 변경되고,

useEffect를 통해 해당 상태가 변경될 때마다 기본적으로 clean up 함수를 통해 Timeout을 clear 하는데,

이 중 startLongPress가 true일 때는 Timeout을 등록하도록 작성했다.

 

interface LongPressButtonProps {
  onLongPress: () => void;
}

const LongPressButton: React.FC<LongPressButtonProps> = ({ children, onLongPress }) => {
  const [longPressTimer, setLongPressTimer] = useState<number | null>(null);
  const buttonRef = useRef<HTMLButtonElement>(null);

  const handleMouseDown = useCallback(() => {
    const timer = window.setTimeout(() => {
      onLongPress();
    }, 1000);
    setLongPressTimer(timer);
  }, [onLongPress]);

  const handleMouseUp = useCallback(() => {
    if (longPressTimer) {
      window.clearTimeout(longPressTimer);
    }
  }, [longPressTimer]);

  return (
    <button
      ref={buttonRef}
      onMouseDown={handleMouseDown}
      onMouseUp={handleMouseUp}
      onTouchStart={handleMouseDown}
      onTouchEnd={handleMouseUp}
    >
      {children}
    </button>
  );
};

export default LongPressButton;

문득 궁금해져서 ChatGPT에게도 물어봤다.

일단 기본적으로 mouse, touch 이벤트를 둘 다 지정했는데...

이 경우 별다른 지정을 하지 않으면 모바일 기기에서 화면 터치 시 onMouseDown과 onTouchStart 이벤트가 둘 다 동작한다!

(그 이외의 개념은 비슷하다. 다만 useEffect 대신 Timeout 자체를 state로 관리해, 각 이벤트마다 등록 및 clear를 해주고 있네)

728x90
반응형

'source-code > React' 카테고리의 다른 글

Suspense를 통한 Render-as-you-fetch  (0) 2023.08.16
React Hook Form 기반 유효성 버튼 구현  (0) 2023.08.16
Inversion of Control  (0) 2023.08.16
Reconciliation (재조정)  (0) 2023.08.16
React Query - Placeholder, Initial Data  (0) 2023.08.16