개요
모바일 기기는 가상 키보드, 터치 상호작용, 다른 IME 동작, 뷰포트 제약으로 인해 고유한 도전과제를 제시합니다. 이 가이드는 모바일 특수 고려사항을 다룹니다.
주요 도전과제:
- 가상 키보드가 뷰포트 크기 조정
- 터치 선택이 마우스보다 덜 정확함
- iOS Safari에서 한국어 IME의 경우 조합 이벤트가 발생하지 않음
- 텍스트 예측과 자동 수정이 입력을 방해함
- 다른 키보드 앱이 다른 동작을 함
가상 키보드
가상 키보드 표시 및 뷰포트 변경을 처리합니다.
키보드 감지
class VirtualKeyboardDetector {
#viewportHeight = window.visualViewport?.height || window.innerHeight;
#isKeyboardVisible = false;
constructor(editor: Editor) {
this.#editor = editor;
this.#setupDetection();
}
#setupDetection() {
if (!window.visualViewport) {
// visualViewport가 없는 브라우저를 위한 대체
window.addEventListener('resize', () => this.#detectKeyboard());
return;
}
window.visualViewport.addEventListener('resize', () => {
const newHeight = window.visualViewport.height;
const heightDiff = this.#viewportHeight - newHeight;
if (heightDiff > 150) {
// 키보드 표시됨
this.#onKeyboardShow();
} else if (heightDiff < -150) {
// 키보드 숨김
this.#onKeyboardHide();
}
this.#viewportHeight = newHeight;
});
}
#onKeyboardShow() {
this.#isKeyboardVisible = true;
this.#scrollToCursor();
this.#adjustLayout();
}
#onKeyboardHide() {
this.#isKeyboardVisible = false;
this.#restoreLayout();
}
#scrollToCursor() {
const selection = window.getSelection();
if (!selection || selection.rangeCount === 0) return;
const range = selection.getRangeAt(0);
const rect = range.getBoundingClientRect();
const viewportHeight = window.visualViewport?.height || window.innerHeight;
if (rect.bottom > viewportHeight) {
range.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
}
}뷰포트 조정
// 키보드가 나타날 때 에디터 높이 조정
function adjustEditorForKeyboard(editor: HTMLElement) {
const viewport = window.visualViewport;
if (!viewport) return;
const keyboardHeight = window.innerHeight - viewport.height;
if (keyboardHeight > 0) {
// 키보드가 보임
editor.style.maxHeight = `${viewport.height - 100}px`;
editor.style.overflowY = 'auto';
} else {
// 키보드가 숨김
editor.style.maxHeight = '';
editor.style.overflowY = '';
}
}터치 선택
터치 기반 텍스트 선택을 처리합니다.
// 터치 선택 정확도 개선
class TouchSelectionHandler {
constructor(editor: Editor) {
this.#editor = editor;
this.#setupTouchHandlers();
}
#setupTouchHandlers() {
let touchStart: Touch | null = null;
this.#editor.element.addEventListener('touchstart', (e) => {
touchStart = e.touches[0];
});
this.#editor.element.addEventListener('touchend', (e) => {
if (!touchStart) return;
// 선택 영역 업데이트 대기
setTimeout(() => {
const selection = window.getSelection();
if (selection && selection.rangeCount > 0) {
this.#adjustSelection(selection.getRangeAt(0));
}
}, 100);
});
}
#adjustSelection(range: Range) {
// 필요시 선택 영역 경계 조정
// 터치 선택은 부정확할 수 있음
}
}모바일 입력 처리
모바일 특수 입력 동작을 처리합니다.
텍스트 예측
// 모바일 텍스트 예측 처리
element.addEventListener('beforeinput', (e) => {
if (e.inputType === 'insertFromPredictiveText') {
// 모바일 키보드 텍스트 예측
e.preventDefault();
this.#handlePredictiveText(e.data);
}
});
// 코드 블록에 대한 예측 비활성화
function disablePrediction(element: HTMLElement) {
element.setAttribute('autocorrect', 'off');
element.setAttribute('autocapitalize', 'off');
element.setAttribute('spellcheck', 'false');
element.setAttribute('inputmode', 'text');
}자동 수정
// 자동 수정 감지 및 처리
class AutocorrectHandler {
#lastInput = '';
handleInput(e: InputEvent) {
if (e.inputType === 'insertText') {
// 이것이 자동 수정일 수 있는지 확인
if (this.#isLikelyAutocorrect(e.data)) {
// 자동 수정 처리
this.#handleAutocorrect(e.data);
} else {
this.#lastInput = e.data;
}
}
}
#isLikelyAutocorrect(text: string): boolean {
// 휴리스틱: 텍스트가 입력한 것과 매우 다르면
// 이를 신뢰할 수 있게 감지하는 것은 어려움
return false;
}
}iOS 특수 문제
iOS Safari는 고유한 동작을 가집니다.
iOS Safari 문제:
- 한국어 IME에 대해 조합 이벤트가 발생하지 않음
- 에디터가 포커스를 잃을 때 선택 영역이 손실될 수 있음
- 가상 키보드 동작이 Android와 다름
// iOS Safari 조합 해결책
function isIOS() {
return /iPad|iPhone|iPod/.test(navigator.userAgent);
}
function isSafari() {
return /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
}
// iOS Safari에서 특정 키에 대해 항상 기본 동작 허용
if (isIOS() && isSafari()) {
element.addEventListener('keydown', (e) => {
if (['Enter', 'Backspace', 'Delete'].includes(e.key)) {
// 브라우저 기본 동작 허용
return;
}
});
}Android 특수 문제
Android 브라우저는 고유한 특성을 가집니다.
// Android Chrome 특수 처리
function isAndroid() {
return /Android/.test(navigator.userAgent);
}
// 다른 키보드 앱 처리
// Gboard, SwiftKey, Samsung Keyboard는 다른 동작을 가짐
function handleAndroidKeyboard() {
// 일부 키보드는 입력 처리를 방해할 수 있음
// 다른 키보드 앱으로 테스트
}모범 사례
- 에뮬레이터뿐만 아니라 실제 기기에서 테스트
- 키보드가 나타날 때 뷰포트 변경 처리
- 커서를 보이게 스크롤
- 코드 블록에 대한 자동 수정 비활성화
- 다른 키보드 앱으로 테스트
- 터치 선택 부정확도 처리
- 모바일 성능 제약 고려