많은 채팅 인터페이스는 사용자가 <textarea>에서 Enter를 누르면 메시지를 제출합니다.
이 방법은 라틴어 키보드 입력 (예: 영어)을 직접 사용하는 사용자에게는 효과적이지만, 입력 방식 편집기 (IME)를 사용하여 일본어, 중국어, 한국어와 같은 언어로 텍스트를 작성하는 사용자에게는 작동하지 않습니다.
이러한 상황에서는 Enter/Return 키를 사용하여 현재 문자 변환 후보를 확인합니다. 맞춤 JavaScript가 Enter에서 keydown를 수신 대기하고 즉시 제출하는 경우 사용자가 문자를 변환하는 동안 메시지가 전송되어 불완전하거나, 단편적이거나, 잘못된 메시지가 전송됩니다.
컴포지션 활성 검사는 여러 줄 <textarea> 필드뿐만 아니라 Enter에서 작동하는 모든 맞춤 keydown 핸들러에 적용됩니다. 검색 및 찾기 표시줄, 인라인 이름 바꾸기 필드, 모달 대화상자 필드, 명령어 팔레트와 같이 자체 Enter 처리가 있는 한 줄 <input> 필드는 <textarea>와 동일한 IME 확인 Enter 키다운을 수신하며 isComposing는 true로 설정됩니다. 커스텀 Enter 핸들러가 없는 <input>에서 <form>의 네이티브 암시적 제출은 명시적 처리가 필요하지 않습니다. 브라우저는 Enter 키 입력이 IME에 의해 사용될 때 암시적 제출을 억제하기 때문입니다.
구현 전략
맞춤 제출 기능이 있는 <textarea> 또는 <input>의 경우 콘텐츠를 제출하기 전에 KeyboardEvent의 네이티브 isComposing 속성을 확인합니다. event.preventDefault()도 호출합니다. Enter의 기본 작업은 <textarea>에 새 줄을 삽입하고 <form> 내의 <input>에서 브라우저의 암시적 제출을 트리거하여 두 번째로 제출합니다.
<form id="chat-form">
<label for="chat-input" class="visually-hidden">Message</label>
<textarea id="chat-input" placeholder="Type a message..."></textarea>
<button type="submit" id="send-button">Send</button>
</form>
const textarea = document.getElementById('chat-input');
const form = document.getElementById('chat-form');
textarea.addEventListener('keydown', (event) => {
if (event.key === 'Enter' && !event.shiftKey) {
// Prevent the default action (newline in a textarea, implicit form submission in an input)
event.preventDefault();
// If the user is composing text, return early.
if (event.isComposing) {
return;
}
form.requestSubmit();
}
});
한 줄 <input>의 경우 !event.shiftKey 조건 없이 동일한 핸들러를 사용합니다. 이 조건은 Shift+Enter가 <textarea>에 줄바꿈을 삽입하기 때문에 존재합니다.
접근성 및 테스트
- 명시적 제출 버튼: 항상
<button type="submit">요소를 포함합니다. 키보드 단축키는 보조 도구이며 기본 양식 제출 경로를 대체해서는 안 됩니다. - 접근성 입력: 모든
<input>및<textarea>요소가 일치하는id및for속성을 사용하여 프로그래매틱 방식으로<label>와 연결되어 있는지 확인합니다.
대체 전략
최근 버전의 Safari에서는 이벤트 순서 지정 문제 (WebKit 버그 165004)로 인해 기본 이벤트가 반대 순서로 전달되었음에도 불구하고 Enter keydown를 확인하기 전에 compositionend가 스크립트 핸들러에 전달되었습니다. keydown 핸들러가 실행될 때까지 event.isComposing는 이미 false로 재설정되어 있었으므로 표준 검사만으로는 조기 제출을 방지할 수 없었을 것입니다.
이전 버전의 Safari 및 기타 플랫폼에서 교차 브라우저 호환성을 지원해야 하는 경우 다음 대체 전략 중 하나를 채택하세요.
전략 1: keyCode === 229 검사 추가 (권장)
event.isComposing를 event.keyCode === 229 확인과 페어링하면 Safari의 순서가 맞지 않는 Enter 키다운 확인을 안정적으로 포착할 수 있습니다. 이 키는 event.key === 'Enter' 게이트 아래에 중첩되어 있으므로 일반 문자 레이아웃 입력에 229를 사용할 수 있는 모바일 가상 키보드로부터 안전합니다.
textarea.addEventListener('keydown', (event) => {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
// Block submission if composing natively or if keyCode is 229
if (event.isComposing || event.keyCode === 229) {
return;
}
form.requestSubmit();
}
});
전략 2: event.timeStamp 기간 해결 방법 (대안)
keyCode와 같은 지원 중단된 API의 사용을 엄격하게 금지하는 코드베이스의 경우 또는 특정 타겟 환경에서 229 검사에 알려진 문제가 있는 경우 브라우저에서 보고한 이벤트 전달 타임스탬프를 대신 추적할 수 있습니다.
Safari는 확인용 Enter keydown 이벤트를 compositionend 이벤트와 매우 가깝게 (보통 5ms 이내, 핸들러 전송 순서 반전으로 인해 keydown 타임스탬프가 약간 더 이른 경우도 있음) 전달하므로 시간 차이를 확인하는 것은 매우 안정적이며 표준 조건에서 모바일 가상 키보드에서 거짓양성을 트리거할 가능성이 낮습니다.
let lastCompositionEndAt = null;
textarea.addEventListener('compositionend', (event) => {
// IMPORTANT: Always use event.timeStamp, not Date.now() or performance.now().
// Handler-time measurements are vulnerable to drift when the main thread is blocked.
lastCompositionEndAt = event.timeStamp;
});
textarea.addEventListener('keydown', (event) => {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
if (event.isComposing) {
return;
}
// Block submission if the event occurs within a 50ms window of composition ending.
// Math.abs handles Safari's inverted event delivery timing bug.
if (
lastCompositionEndAt !== null &&
Math.abs(event.timeStamp - lastCompositionEndAt) < 50
) {
return;
}
form.requestSubmit();
}
});