현상
iOS Safari에서 일본어/한자 IME를 사용할 때, insertFromComposition이 collapsed된 targetRanges와 함께 발생하지만 정확한 삽입 위치를 나타내지 않습니다. 현재 선택 영역을 기반으로 범위를 재계산해야 조합 텍스트를 정확한 위치에 삽입할 수 있습니다.
재현 예시
- iOS Safari(iPhone/iPad)에서
contenteditable요소에 포커스합니다. - 일본어 IME를 활성화합니다.
- 일본어/한자 텍스트 조합을 시작합니다 (예: “に” → “ほ” → “ん”을 입력하여 “日本” 조합).
- 조합을 업데이트하거나 한자로 변환합니다.
inputType: 'insertFromComposition'이 있는beforeinput이벤트를 관찰합니다.getTargetRanges()를 확인합니다 - collapsed(startOffset === endOffset)이지만 정확한 삽입 위치를 나타내지 않습니다.- 핸들러가
targetRanges를 그대로 신뢰하면 텍스트가 잘못된 위치에 삽입됩니다. - 현재 선택 영역을 기반으로 재계산하면 정확한 위치에 삽입됩니다.
관찰된 동작
일본어/한자 조합 텍스트를 업데이트할 때:
-
beforeinput 이벤트:
inputType: 'insertFromComposition'isComposing: truedata: '日本'(새로운 조합 텍스트)getTargetRanges()는 collapsed된 범위를 반환합니다 (startOffset === endOffset)- collapsed된 범위는 조합 텍스트가 삽입되어야 할 정확한 위치를 나타내지 않습니다
-
범위를 그대로 신뢰하는 경우:
- 텍스트가 잘못된 위치에 삽입될 수 있습니다
- 조합 텍스트가 의도한 위치 앞이나 뒤에 나타날 수 있습니다
- 조합 업데이트가 실패하거나 잘못된 결과를 생성할 수 있습니다
-
범위를 재계산하는 경우:
window.getSelection()을 사용하여 현재 선택 위치를 가져옵니다- 현재 선택 영역을 기반으로 삽입 위치를 재계산합니다
- 텍스트가 정확한 위치에 삽입됩니다
- 조합 업데이트가 올바르게 작동합니다
예상 동작
insertFromComposition의targetRanges는 정확한 삽입 위치를 나타내야 합니다targetRanges가 collapsed이고 부정확한 경우 재계산이 가능해야 합니다- 핸들러는 현재 선택 영역을 사용하여 정확한 삽입 위치를 결정할 수 있어야 합니다
- 조합 텍스트는 정확한 위치에 삽입되어야 합니다
영향
- 잘못된 위치 지정: collapsed된
targetRanges를 그대로 신뢰하는 핸들러는 텍스트를 잘못된 위치에 삽입합니다 - 조합 중단: 조합 업데이트가 실패하거나 잘못된 결과를 생성할 수 있습니다
- 플랫폼별 버그: Desktop Safari에서 작동하는 코드가 iOS Safari에서 실패할 수 있습니다
- IME별 버그: iOS Safari에서 한글 IME와 작동하는 코드가 일본어/한자 IME에서 실패할 수 있습니다
브라우저 비교
- iOS Safari (일본어/한자): 재계산이 필요한 collapsed된
targetRanges와 함께insertFromComposition발생 - Desktop Safari: 정확한 collapsed된
targetRanges와 함께insertFromComposition발생 (그대로 신뢰) - iOS Safari (한글):
insertFromComposition이 발생하지 않음 - Chrome/Edge: 일반적으로
insertFromComposition대신insertCompositionText사용 - Firefox: 동작이 다양하지만 일반적으로 Chrome과 더 일관됨
해결 방법 및 참고사항
-
collapsed된 범위 재계산: iOS Safari에서 현재 선택 영역을 기반으로 재계산합니다:
element.addEventListener('beforeinput', (e) => { if (e.inputType === 'insertFromComposition') { const targetRanges = e.getTargetRanges?.() || []; if (targetRanges.length > 0) { const range = targetRanges[0]; if (range.collapsed) { // 현재 선택 영역을 기반으로 재계산 const selection = window.getSelection(); const currentRange = selection?.rangeCount ? selection.getRangeAt(0) : null; if (currentRange) { // targetRanges 대신 currentRange 사용 handleCompositionInsertion(currentRange); } } else { // collapsed가 아니면 targetRanges를 그대로 사용 handleCompositionInsertion(range); } } } }); -
플랫폼 및 IME 감지: iOS Safari에서 일본어/한자 IME를 감지합니다:
const isIOSSafari = /iPhone|iPad|iPod/.test(navigator.userAgent) && /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent); const isJapaneseIME = /* 일본어 IME 감지 */; if (isIOSSafari && isJapaneseIME && e.inputType === 'insertFromComposition') { // collapsed된 범위 재계산 } -
현재 선택 영역 사용:
targetRanges가 collapsed일 때 항상window.getSelection()을 사용하여 정확한 위치를 가져옵니다