Skip to content

Commit 60a93a7

Browse files
26.02.12 ios-safari-virtual-keyboard-policy
1 parent af4493d commit 60a93a7

1 file changed

Lines changed: 106 additions & 0 deletions

File tree

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# iOS Safari 가상 키보드 정책과 해결 방법
2+
3+
## 문제 상황
4+
5+
모바일 웹에서 검색 페이지로 이동 시 input에 자동으로 포커스가 잡히고 가상 키보드가 올라와야 하는 UX를 구현해야 했다.
6+
7+
## iOS Safari의 가상 키보드 정책
8+
9+
> **`input.focus()`는 사용자의 직접적인 터치 이벤트의 동기 실행 흐름(call stack) 안에서 호출되어야만 가상 키보드가 활성화된다.**
10+
11+
즉, 아래 조건을 모두 만족해야 한다:
12+
13+
1. 사용자의 **물리적 터치(탭)** 이벤트가 발생해야 한다
14+
2. 해당 이벤트 핸들러의 **동기 실행 흐름** 안에서 `focus()`가 호출되어야 한다
15+
3. 비동기 작업(`setTimeout`, `Promise`, `requestAnimationFrame` 등)을 거치면 gesture 체인이 끊어진다
16+
17+
### 동작하는 경우 vs 동작하지 않는 경우
18+
19+
```
20+
// O: 사용자 탭 → 동기적 focus → 키보드 활성화
21+
<input onFocus={handleNavigation} />
22+
// 사용자가 input을 직접 탭 → focus 이벤트 발생 → 키보드 활성화
23+
24+
// X: 사용자 탭 → 비동기 네비게이션 → mount → focus → 키보드 무시
25+
<button onClick={() => {
26+
router.push('/검색 페이지'); // 비동기 네비게이션
27+
}} />
28+
// 이후 검색 페이지의 autoFocus → gesture 체인 끊김 → 키보드 활성화 안 됨
29+
```
30+
31+
## 해결 방법: 투명 input 오버레이 패턴
32+
33+
아이콘 버튼 위에 투명한 `readOnly` input을 겹쳐 놓아, 사용자의 탭이 직접 input의 focus 이벤트로 이어지도록 한다.
34+
35+
### Before (키보드 미활성화)
36+
37+
```tsx
38+
<Button onClick={moveToSearch}>
39+
<Icon size="md" icon="search" />
40+
</Button>
41+
```
42+
43+
사용자가 버튼을 탭하면 `onClick``router.push('/검색 페이지')` → 검색 페이지 마운트 → `autoFocus`로 focus 시도. 하지만 이 시점에서는 이미 터치 이벤트의 동기 실행 흐름이 끊어져 iOS Safari가 키보드를 차단한다.
44+
45+
### After (키보드 활성화)
46+
47+
```tsx
48+
<Button className="relative !p-[14px]">
49+
<Icon size="md" icon="search" />
50+
<input readOnly className="absolute inset-0 opacity-0" onFocus={moveToSearch} />
51+
</Button>
52+
```
53+
54+
사용자가 탭하면 투명 input이 직접 포커스를 받아 iOS가 이를 "사용자가 입력 필드를 터치한 이벤트"로 인식한다. `onFocus`에서 페이지 이동이 일어나고, 검색 페이지의 input이 마운트되면서 키보드가 유지된다.
55+
56+
### 핵심 포인트
57+
58+
| 속성 | 역할 |
59+
| ------------------ | ---------------------------------------------- |
60+
| `readOnly` | 탭 시 입력 커서가 나타나지 않도록 방지 |
61+
| `absolute inset-0` | 아이콘 버튼 영역 전체를 덮어 탭 영역 확보 |
62+
| `opacity-0` | 시각적으로 보이지 않지만 포커스는 받을 수 있음 |
63+
| `onFocus` | `onClick` 대신 사용하여 iOS gesture 체인 유지 |
64+
65+
## 시도했던 다른 방법들
66+
67+
### 1. 검색 페이지 mount 시점에서 focus
68+
69+
```tsx
70+
useEffect(() => {
71+
inputRef.current?.focus();
72+
}, []);
73+
```
74+
75+
- **문제점**: 보편적으로 사용했던 방법. `useEffect`는 비동기 실행이므로 터치 이벤트의 gesture 체인이 이미 끊어진 상태. iOS Safari가 키보드를 차단함
76+
77+
### 2. 임시 input 동적 생성
78+
79+
```ts
80+
const searchClick = () => {
81+
const tempInput = document.createElement('input');
82+
document.body.appendChild(tempInput);
83+
tempInput.focus(); // 키보드 활성화
84+
router.push('/검색 페이지');
85+
setTimeout(() => tempInput.remove(), 2000);
86+
};
87+
```
88+
89+
- **문제점**: 키보드가 페이지 전환보다 먼저 나타나 UX가 어색함
90+
- **문제점**: `router.push`와 동적 input의 focus가 충돌하여 라우터 이동이 실패하는 경우 발생
91+
92+
### 2-1. requestAnimationFrame으로 라우터 지연
93+
94+
```ts
95+
tempInput.focus();
96+
requestAnimationFrame(() => {
97+
setTimeout(() => tempInput.remove(), 2000);
98+
router.push('/검색 페이지');
99+
});
100+
```
101+
102+
- **문제점**: 라우터 이동은 가능하지만 가상 키보드가 검색 화면으로 이동 전에 미리 노출되는 상황 발생
103+
104+
## 결론
105+
106+
iOS Safari의 가상 키보드 정책은 "사용자가 직접 input을 터치해야 키보드가 뜬다"로 요약된다. 페이지 마운트 시에 자동으로 키보드를 띄우려면, **전환 트리거 자체가 input의 직접적인 터치 이벤트**여야 한다.

0 commit comments

Comments
 (0)