블로그 목록

expo-haptics로 앱에 촉각 피드백 넣기 — 버튼 탭이 밋밋할 때

앱을 만들다 보면 어느 순간 "기능은 다 되는데 뭔가 싸구려 같다"는 느낌이 든다. 네이티브 앱을 써보면 버튼을 누를 때마다 손끝에 미세한 진동이 오는데, 내 앱은 그게 없으니 밋밋했던 거다. 이 촉각 피드백(haptics)이 생각보다 완성도 체감에 크게 작용한다.

Expo에서는 expo-haptics로 이걸 몇 줄이면 붙일 수 있다.

설치

npx expo install expo-haptics

네이티브 코드가 포함돼 있어서 Expo Go에서도 대부분 잘 돌지만, 실기기에서 테스트해야 한다. 시뮬레이터·에뮬레이터는 진동을 못 준다.

세 종류를 구분해서 쓴다

처음엔 그냥 아무 데나 진동을 넣었는데, 그러면 오히려 정신없다. API가 용도별로 나뉘어 있는 데는 이유가 있다.

  • impactAsync — 버튼 탭, 토글 같은 물리적 상호작용. Light / Medium / Heavy 세기 선택.
  • notificationAsync — 작업 결과 알림. Success / Warning / Error.
  • selectionAsync — 피커나 세그먼트를 스크롤하며 값이 바뀔 때. 아주 미세하다.
import * as Haptics from 'expo-haptics';
import { Pressable, Text } from 'react-native';

function SubmitButton({ onSubmit }: { onSubmit: () => Promise<boolean> }) {
  const handlePress = async () => {
    // 탭한 순간 가벼운 피드백
    await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);

    const ok = await onSubmit();

    // 결과에 맞는 알림 피드백
    await Haptics.notificationAsync(
      ok
        ? Haptics.NotificationFeedbackType.Success
        : Haptics.NotificationFeedbackType.Error,
    );
  };

  return (
    <Pressable onPress={handlePress}>
      <Text>제출</Text>
    </Pressable>
  );
}

탭할 때 가볍게 한 번, 결과가 나오면 성공/실패에 맞게 한 번 더. 이 조합만으로도 앱이 확 살아난다.

남발하면 역효과

주의할 점 하나. 스크롤이나 리스트 아이템처럼 자주 발생하는 이벤트에 impactAsync를 넣으면 손이 얼얼하고 배터리도 닳는다. 값이 실제로 "확정"되는 순간에만 주는 게 좋다. 피커 스크롤에는 selectionAsync처럼 미세한 걸 쓰는 이유도 이거다.

그리고 Android는 기기마다 진동 모터 품질 차이가 커서, iOS만큼 세밀한 표현은 안 된다. 접근성 설정에서 시스템 햅틱을 꺼둔 사용자도 있으니, 피드백은 어디까지나 "덤"으로 설계해야 한다. 진동이 없어도 기능은 다 동작해야 한다는 뜻이다.


기능 개발이 끝난 뒤 30분만 투자해서 주요 버튼과 성공/실패 지점에 햅틱을 얹어보자. 코드는 몇 줄인데 체감 완성도는 눈에 띄게 올라간다. 정작 사용자는 "왜 좋은지"는 설명 못 해도 손끝으로는 느낀다.