개발 기술/사소하지만 놓치기 쉬운 개발 지식

모바일에서 연속 입력시 값이 무시되는 이유(feat. click, touch, pointer event)

by GicoMomg 2024. 10. 19.

1. 들어가며

현재 우리 서비스는 모바일 환경에서 가상 키패드를 제공하며, 가상 키패드는 click 이벤트로 입력을 처리하고 있다.
그러나 특정 상황에서 키패드 입력이 누락되는 문제가 발생하고 있었다.

이 문제는 사용자가 입력 버튼을 빠르게 번갈아가며 클릭할 때 발생했는데, 웹 환경에서는 문제가 없었지만, 모바일 환경에서만 이 문제가 나타났다.
아래 gif는 문제를 재현한 모습이다. gif상 버튼이 가끔씩 눌리는 것처럼 보이지만, 실제로는 빠르게 번갈아가면서 누르고 있었다ㅎㅎ

 

도대체 왜 모바일 환경에서만 입력 누락 문제가 발생하는 걸까?

이번 시간에는 모바일 환경에서 연속 입력이 무시되는 이유와 click 이벤트와의 연관성을 살펴보겠다.
이 글은 아래 목차 순서대로 진행된다.

1. 들어가며
2. 모바일 환경에서 연속 입력이 무시되는 이유 
    문제 1. click 이벤트에서 300ms 지연이 발생해서
        (1) 구형 기기는 클릭 지연이 발생할 수 있다.
        (2) 더블 탭을 차단하는 2가지 방법
    문제2. click 이벤트의 처리 순서가 마지막이라서
    (1) 모바일에서 발생하는 이벤트의 처리 순서
    (2) 방법1, touchend + click 이벤트로 입력 누락을 줄이자
    (3) 방법2, pointerup이벤트로 입력 누락을 줄이자
3. 마치며…



2. 모바일 환경에서 연속 입력이 무시되는 이유

문제 1. click 이벤트에서 300ms 지연이 발생해서

(1) 구형 기기는 클릭 지연이 발생할 수 있다.

  • 왜 모바일 환경에서 연속 입력시, 일부 입력이 누락된걸까? 그 이유는 click 이벤트의 특징 때문이다.
  • 과거 모바일 브라우저는 더블 탭 확대 기능을 위해, click 이벤트가 발생하기 전에 약 300ms의 지연을 두었다.
  • 그래서 버튼을 빠르게 연속해서 클릭하면, 이벤트가 누락되거나 지연이 발생하는 경우가 있다.
  • 다행히도 대부분의 최신 모바일 브라우저는 더블 탭 확대가 비활성화된 경우 지연이 발생하지 않는다.
  • 아래 이미지는 Chorme developer 사이트의 내용 중 일부이다.
  • 내용을 보면 알 수 있듯이, “과거, 더블 탭 동작을 인식하기 위해 click시 300ms 지연을 주었는데, Chrome은 2014, IOS는 2016.03월 부터 지연 문제를 해결”했다고 한다.

 

  • 지연 문제를 해결하기 전 후 예시를 보자.
  • gif상 좌측은 지연 문제가 있던 모습, 우측은 지연 문제가 해결된 모습이다.

 

하지만 만약 제공하는 서비스의 최소 버전이 2014년 이전이라면?

  • 사용자가 구형 기기를 사용하는 경우, 300ms 지연이 발생할 수 있다.
  • 이 경우, 더블 탭 동작을 차단해서 해결할 수 있다.

 

(2) 더블 탭을 차단하는 2가지 방법

  • 첫 번째 방법은 페이지의 <head>content="width=device-width"를 추가하는 방법이다.
<meta name="viewport" content="width=device-width">
  • 이렇게 하면 표시 영역의 너비가 기기와 동일하게 설정되어, 두 번 탭하여 확대/축소 기능이 삭제된다.

 

  • 하지만, head 태그를 수정했는데도 지연이 발생한다면?
  • 이 경우 css로 touch-action: manipulation를 적용해, 두 번 탭하여 확대/축소 기능이 차단하자.
html {
   touch-action: manipulation;
}



문제2. click 이벤트의 처리 순서가 마지막이라서

  • 모바일에서는 click 이벤트 외에도 다양한 이벤트(touchend, pointerup 등)가 발생한다.
  • 그래서 이벤트들이 중복되거나 동시에 발생하면 입력이 누락될 수 있다.
  • 그럼 모바일에서는 어떤 이벤트가 발생하기에 입력 누락이 발생하는 걸까? 모바일에서 발생하는 이벤트의 처리 순서와 특징을 알아보자.

(1) 모바일에서 발생하는 이벤트의 처리 순서

모바일에서 사용자가 버튼을 클릭(터치)할 때 발생하는 이벤트 순서는 다음과 같다:

  1. touchstart: 사용자가 화면을 터치하기 시작할 때
  2. pointerdown: 터치, 마우스, 스타일러스를 포함한 모든 포인터 입력을 통합적으로 처리
  3. mousedown: 마우스 버튼이 눌렸을 때 발생하며, 터치 입력 시에도 발생할 수 있음
  4. touchend: 사용자가 화면에서 손가락을 뗄 때 발생
  5. pointerup: 포인터가 요소에서 떨어졌을 때 발생
  6. mouseup: 마우스 버튼이 놓였을 때 발생하며, 터치 입력 시에도 발생할 수 있음
  7. click: 사용자가 요소를 클릭(터치)했을 때 최종적으로 발생하는 이벤트
  • 그림을 보면 알 수 있듯이 사용자가 버튼을 클릭(터치)하면 여러 이벤트들이 연속해서 발생하고, click 이벤트가 가장 마지막에 처리된다.
  • 그래서 빠르게 연속 입력 시 이전 터치 이벤트가 모두 처리되기 전에 새로운 입력이 들어오면 일부 입력이 무시될 수 있다. 특히, 이벤트 핸들러에 무거운 연산이 포함되어 있을 때 입력 무시 현상이 더 자주 발생할 수 있다.

 

그럼 어떻게 해야 입력 누락을 줄일 수 있을까? 다른 이벤트를 써보자!

  • click 이벤트보다 더 처리가 빠른 이벤트로 교체해보자.
  • 우선 모바일에서 가능한 이벤트 중 처리가 빠른 이벤트는 touchend, pointerup이다.

  • touchend의 경우 제일 처리 순서가 빠르지만, 웹 환경에서는 이벤트가 작동하지 않는 단점이 있다.
  • 그에 반해, pointerdown은 웹 & 모바일 둘 다 가능하나, 버전 호환성 문제가 있고 touchend보다 처리 순서가 느리다.
  • 각각을 실제로 구현해보면서 어떤 차이가 있는지 확인해보자.

 

(2) 방법1, touchend + click 이벤트로 입력 누락을 줄이자

  • touchend 이벤트에 이벤트 핸들러를 등록하고, 웹에서도 동작할 수 있게 click 이벤트에도 이벤트 핸들러를 등록했다.
let inputValue = '';
const display = document.getElementById('display');

document.addEventListener('DOMContentLoaded', onEvent);

// (a) 버튼 리스너 설정
function onEvent() {
  const buttons = document.querySelectorAll('.keypad button');

  buttons.forEach((button) => {
    button.addEventListener('touchend', onTouchend, { passive: false });
    button.addEventListener('click', handleInput);
  });
}

// (b) 터치 이벤트 처리
function onTouchend(e) {
  event.preventDefault(); // 중복 호출 차단
  handleInput(event);
}

// (c) 입력 처리 및 표시
function handleInput(event) {
  const value = event.target.getAttribute('data-value');

  inputValue += value;
  display.textContent = `입력된 숫자: ${inputValue}`;
}
번호 설명
(a) 버튼 리스너 설정 - buttons.keypad 클래스 안의 모든 button 요소들을 선택한다.

- touchend
: 터치가 끝날 때 호출되며, 모바일 환경에서 사용된다.
: passive: false 옵션을 지정해 preventDefault()를 사용할 수 있도록 설정한다.
: preventDefault() 가 있어야 클릭 이벤트와 터치 이벤트가 중복 호출되는 걸 막을 수 있다.

- click
: 클릭 이벤트가 발생하면 handleInput 함수가 호출한다.
(b) 터치 이벤트 처리 - 터치가 끝날 때 기본 클릭 이벤트를 막기 위해 event.preventDefault()를 호출한다.
- handleInput 함수를 호출하여 터치로 발생한 입력을 처리한다.
(c) 입력 처리 및 표시 - event.target에서 data-value속성을 읽어 버튼의 값을 가져온다.
- 이 값을 inputValue에 추가하고, display요소의 텍스트를 업데이트하여 사용자가 입력한 값을 표시한다.

 

  • 실제 구현된 코드는 아래와 같다.

See the Pen click + touchend by KumJungMin (@kumjungmin) on CodePen.

 

버튼을 빠르게 번갈아 가면서 클릭해보자!

  • 왼쪽은 클릭일 때, 오른쪽은 touchend 방식이다.
  • gif를 보면 알 수 있듯이, touchend 이벤트가 입력 누락이 더 적을 걸 알 수 있다.



(3) 방법2, pointerup이벤트로 입력 누락을 줄이자

document.addEventListener('DOMContentLoaded', onEvent);

// (a) 버튼 리스너 설정
function onEvent() {
  const buttons = document.querySelectorAll('.keypad button');

  buttons.forEach((button) => {
    button.addEventListener('pointerup', handleInput);
  });
}

let inputValue;
const display = document.getElementById('display');

// (b) 입력 처리 및 표시
function handleInput(event) {
  const value = event.target.getAttribute('data-value');
  inputValue += value;
  display.textContent = `입력된 숫자: ${inputValue}`;
}
번호 설명
(a) 버튼 리스너 설정 - buttons.keypad 클래스 안의 모든 button 요소들을 선택한다.
- pointerup: 터치나 클릭이 끝날 때 호출되며, 모바일 환경에서 사용된다.
(b) 입력 처리 및 표시 - event.target에서 data-value속성을 읽어 버튼의 값을 가져온다.
- 이 값을 inputValue에 추가하고, display요소의 텍스트를 업데이트하여 사용자가 입력한 값을 표시한다.

 

  • 실제 구현된 코드는 아래와 같다.

See the Pen pointerdown example by KumJungMin (@kumjungmin) on CodePen.

 

버튼을 빠르게 번갈아 가면서 클릭해보자!

  • 왼쪽은 클릭일 때, 오른쪽은 pointerup 방식이다.
  • gif를 보면, pointerup 이벤트가 click보다 입력 누락이 적다는 걸 알 수 있다.



그럼 click 이벤트 대신, 어떤 이벤트를 쓰는 게 좋을까?

  • 입력 누락은 세 이벤트 중 touchend가 제일 적었다. 하지만 만약 서비스가 웹과 모바일을 둘 다 제공하는 경우 touchend + click을 함께 구현해야한다.
  • 이에 반해 pointerup은 웹과 모바일 모두 작동하지만, 브라우저 호환성 체크가 필요하다.
  • 그래서 서비스 성격과 최소 호환 버전에 맞춰, pointerup 혹은 touchend를 선택해도 좋을 듯 하다.



3. 마치며…

이번 시간에는 모바일 환경에서 버튼을 빠르게 클릭했을 때 일부 입력이 무시되는 문제를 살펴보았다.
이는 click 이벤트의 지연과 중복 처리로 인해 발생한 문제로, pointertouch 이벤트를 활용하여 해결할 수 있었다.

또한 과거에는 더블탭 동작을 구분하기 위해 click 이벤트에 300ms 지연이 있었으나, 최신 브라우저에서는 이러한 지연이 개선되었다.
만약 여전히 300ms 지연이 발생한다면, <head> 태그나 CSS 속성을 활용하여 더블탭 동작을 막아 해결할 수 있다.

이처럼 사소한 차이지만 어떤 이벤트를 선택하느냐에 따라 사용자 경험이 크게 달라질 수 있다.
이 외에도 다양한 방법들이 있을 수 있어, 서비스를 구현하기 전에 탐구해보는 걸 추천한다. 🙂

 

반응형

댓글