Enter で送信、Shift+Enter のときは改行といった処理を行う際に、IME入力確定時のEnterとの区別が環境差異で大変だったのでまとめました。
前提
-
keydownで発火するイベントKeyboardEventでは、Enterキー押下時に.keyプロパティの値が環境によって異なり、仮想キーを送信するWindows以外では、IME確定時にもEnterとなるため判別困難 -
composition*イベントによるフラグ判定や、KeyboardEvent.isComposingプロパティによってIME変換ステータスが検知できるはずだが、Safariでは他ブラウザと異なりcompositionend→keydownの順に発火するため、 入力確定時はisComposing==falseになる -
KeyboardEvent.keyCodeはWindows以外でも 229 となるため共通して判定に活用できるが、廃止プロパティであり非推奨 -
keypressイベントは文字入力を伴わないキー入力で発火しないため、IME確定では発火せずに改行入力でのEnterキー押下のみを捕捉できるが、廃止イベントであり非推奨
Enterキー押下を捕捉する
beforeinput で発火する InputEventイベントを用いたイベント捕捉を行います。
InputEvent.inputType により改行入力を検出することができます。
改行挿入時の inputType の値は環境及び入力対象によって異なるため、予め検討する必要がありますが、基本的には両方指定しておけば問題ないと思います。
ref: IME 入力 - ブラウザ間の違い
textArea.addEventListener('beforeinput', (e) => {
const isLineBreak = e.inputType === 'insertLineBreak' || e.inputType === 'insertParagraph';
if (isLineBreak) {
e.preventdefault();
console.log('Submit');
}
});
修飾キーの状態を考慮する
InputEvent は KeyboardEvent と異なり、 .shiftKey プロパティ等で修飾キーの状態を検知することができません。
しかし、 composition* イベントと異なり、keydown → beforeinput → input の順であることがw3c仕様として定義されています。
https://w3c.github.io/uievents/split/keyboard-events.html#event-type-keydown
3.4.1. keydown
This event type MUST be dispatched before the beforeinput, input, and keyup events associated with the same key.
したがって、 keydown で修飾キーの状態を検査しておくことで beforeinput でもその状態を参照できます。
以下は、Shiftを押していないときの改行時に送信を行う例です。
let isShiftPressed = false;
textArea.addEventListener('keydown', (e) => {
isShiftPressed = e.shiftKey;
});
textArea.addEventListener('beforeinput', (e) => {
const isLineBreak = e.inputType === 'insertLineBreak' || e.inputType === 'insertParagraph';
if (isLineBreak && !isShiftPressed) {
e.preventdefault();
console.log('Submit');
}
});
Reactでの注意点
2025年11月現在、Reactではネイティブの beforeinput イベントに対応しておらず、 onBeforeInput を利用する場合、ポリフィルで TextEvent が発火するようになっています。
https://ja.react.dev/reference/react-dom/components/common
onBeforeInput: InputEvent ハンドラ関数。編集可能な要素の値が変更される前に発火します。React はまだネイティブの beforeinput イベントを使用しておらず、他のイベントを使用してポリフィルを試みます。
したがって beforeinput を使いたい場合は useEffect を使って手動でイベントリスナーを設定する必要があります。
その場合リスナーの重複登録を防ぐためクリーンアップする必要があります。
useEffect(() => {
const textAreaElement = textAreaRef.current;
if (!textAreaElement) return;
let isShiftPressed = false;
const handleKeyDown = (e: KeyboardEvent) => {
isShiftPressed = e.shiftKey;
};
textAreaElement.addEventListener('keydown', handleKeyDown);
const handleBeforeInput = (e: InputEvent) => {
const isLineBreak =
e.inputType === 'insertLineBreak' || e.inputType === 'insertParagraph';
if (isLineBreak && !isShiftPressed) {
e.preventDefault();
console.log('Submit');
}
};
textAreaElement.addEventListener('beforeinput', handleBeforeInput);
return () => {
textAreaElement.removeEventListener('keydown', handleKeyDown);
textAreaElement.removeEventListener('beforeinput', handleBeforeInput);
};
}, []);
おわりに
何か修正点や補足等あればコメントお願いします。