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) 모바일에서 발생하는 이벤트의 처리 순서
모바일에서 사용자가 버튼을 클릭(터치)할 때 발생하는 이벤트 순서는 다음과 같다:
touchstart
: 사용자가 화면을 터치하기 시작할 때pointerdown
: 터치, 마우스, 스타일러스를 포함한 모든 포인터 입력을 통합적으로 처리mousedown
: 마우스 버튼이 눌렸을 때 발생하며, 터치 입력 시에도 발생할 수 있음touchend
: 사용자가 화면에서 손가락을 뗄 때 발생pointerup
: 포인터가 요소에서 떨어졌을 때 발생mouseup
: 마우스 버튼이 놓였을 때 발생하며, 터치 입력 시에도 발생할 수 있음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
이벤트의 지연과 중복 처리로 인해 발생한 문제로, pointer
나 touch
이벤트를 활용하여 해결할 수 있었다.
또한 과거에는 더블탭 동작을 구분하기 위해 click
이벤트에 300ms 지연이 있었으나, 최신 브라우저에서는 이러한 지연이 개선되었다.
만약 여전히 300ms 지연이 발생한다면, <head>
태그나 CSS 속성을 활용하여 더블탭 동작을 막아 해결할 수 있다.
이처럼 사소한 차이지만 어떤 이벤트를 선택하느냐에 따라 사용자 경험이 크게 달라질 수 있다.
이 외에도 다양한 방법들이 있을 수 있어, 서비스를 구현하기 전에 탐구해보는 걸 추천한다. 🙂
'개발 기술 > 사소하지만 놓치기 쉬운 개발 지식' 카테고리의 다른 글
웹사이트를 최적화시키는 3가지 기법: 코드 압축, 경량화, 난독화 (4) | 2024.11.18 |
---|---|
[vue] v-show 선언 위치에 따라 렌더링 버그가 발생한다고? (2) | 2024.11.07 |
iOS 모바일의 보안과 이벤트 유실(feat. 사용자 활성화, 이벤트 루프) (20) | 2024.10.03 |
[CSS/JS] ellipsis 말줄임에서 발생할 수 있는 2가지 인터렉션 문제 (0) | 2024.09.19 |
[JS] event.target vs event.currentTarget의 차이 (with. vue slot) (0) | 2024.09.08 |
댓글