症状:日本語を変換確定しただけでフォームが送信される
検索ボックスや入力フォームで、日本語をまだ打ち終えていないのに勝手に送信・検索が走ってしまう。ユーザーは けんさく を漢字に変換して確定したかっただけなのに、その確定のEnterが送信のEnterとして扱われ、中途半端な状態で送信される。日本語入力を使う業務アプリで報告の多い問題です。
ただし、この挙動は ブラウザとIMEの組み合わせに強く依存します。実際に手元で試すと、たとえばモダンなChromeでは変換確定のEnterがIMEに消費され、この問題が起きないことも多いです。一方でSafari(Mac/iOS)のように、確定のEnterが素のEnterとして届く環境では起きるとされています。ブラウザのバージョンアップで挙動が変わることもあるので、「必ず起きる/絶対に起きない」と決めつけず、まず自分の環境で実際にどうなるかを確かめるのが確実です。この記事では、その確認ができるインスペクター付きのデモと、どの環境でも安全になる実装をまとめます。
どこで起きるか:3つのつまずきパターン
確定Enterでの誤送信は、起きる環境では次の3つのどれかの形で現れます。いずれも根っこは同じで、「変換を確定するためのEnterが、素のEnter(送信のEnter)として扱われてしまう」ことです。
パターン1:入力欄が1つだけのフォームの暗黙送信
テキスト入力が1つだけの form は、JavaScriptを一切書かなくてもEnterで送信される(HTMLの暗黙的送信)仕様があります。この暗黙送信が変換確定のEnterでも走るかどうかはブラウザや状況で差が出ます。まずは後述のデモで、自分のブラウザの実挙動を確認するのが確実です。
パターン2:自作の「Enterで送信」ハンドラー
検索ボックスなどでキーイベントを監視し、Enterが押されたら検索や送信を実行する実装。確定Enterを区別していないと、それが素のEnterとして届く環境で処理が走ります。とくに keyup や keypress で拾っている実装は、keydown で拾う場合より誤爆しやすい傾向があります。
パターン3:チャット風の「Enterで送信/Shift+Enterで改行」
メッセージ入力欄で「Enterは送信、Shift+Enterは改行」にする実装。日本語を打つと、変換確定のEnterが送信として扱われ、書きかけのメッセージが飛んでしまう。記事末尾で対処に触れます。
デモ:自分の環境の挙動を確かめる
下は、入力欄が1つだけの検索フォームです(<form> の中に入力欄と検索ボタンがあるだけの、最もありふれた構成)。特別なJavaScriptは書かず、フォームの暗黙送信(入力欄が1つのフォームはEnterで送信される)で検索します。日本語入力(IME)をオンにして、けんさく などと打ち、変換候補が出ている状態でEnterを押して確定してみてください。あわせて、その間にブラウザが発火させたイベントの生の値(key・keyCode・isComposingと submit)を下のインスペクターに記録します。これはあなたのブラウザが実際に返した値です。
お使いのブラウザとIMEによって挙動が分かれます。確定Enterでそのまま検索が走れば [submit] → 検索実行 が赤く記録されます(誤爆)。走らなければ、確定Enterがフォーム送信として扱われていない、という意味です。
<!-- NG: 入力欄が1つのフォーム。Enterで暗黙送信される -->
<form>
<input type="text" name="q">
<button type="submit">検索</button>
</form>
<script>
form.addEventListener('submit', function (e) {
e.preventDefault();
runSearch(); // 変換確定のEnterでも送信される環境がある
});
</script>
インスペクターに [submit] → 検索実行 が赤く出れば、確定Enterがそのままフォーム送信として扱われた(誤爆した)ということです。この挙動はブラウザとIMEの組み合わせで変わります。たとえばWindowsのChromeでは、確定Enterが keydown/keyup ともに key='Process'(keyCode 229)として消費され、暗黙送信も走らないことがあります。その場合はログに submit が出ません。一方、Safariや一部のIME・古い環境では確定Enterが素のEnterとして扱われ送信されます。まず自分の環境がどちらかを、上のログで確認してください。
対策の基本:isComposingで変換中のEnterを無視する
KeyboardEvent.isComposing は、compositionstart と compositionend の間、つまりIMEで変換している最中に発火したイベントで true になります。Enterを送信のトリガーにするなら、まずこのフラグが立っているEnterを無視します。あわせて、古い環境向けに keyCode === 229 も見ておくと安全です。
// OK(簡易版): 変換中・IME処理中のEnterは無視する
input.addEventListener('keydown', function (e) {
if (e.key !== 'Enter') return;
if (e.isComposing || e.keyCode === 229) return; // ← IME確定のEnterを弾く
runSearch();
});
多くのケースはこれで直ります。ポイントは e.key !== 'Enter' で先に絞ってから、IME判定で早期returnすることです。keyCode は非推奨のプロパティですが、この 229 だけは今も判定に使える実用的な値なので併記しています。
失敗デモのように 入力欄が1つの <form> の暗黙送信に頼っていると、Enterのタイミングをコードで制御できません。誤爆する環境では確定Enterがそのまま送信され、それを止める手段がフォーム側にないからです。そこで、暗黙送信は submit イベントで e.preventDefault() して無効化し、検索は上のように keydown で明示的にトリガーする設計にします。こうすれば、確定Enterかどうかを isComposing で判定してから検索できます。
あわせて、Enterの判定は keyup や keypress ではなく keydown で行います。keyup は環境によっては変換確定のEnterでも key='Enter' として発火し、そのとき isComposing は false に戻っているため弾けないからです。
Safariの落とし穴:確定直後のEnterがすり抜ける
簡易版で大半は直りますが、Safariでは compositionend が確定のEnterの keydown より先に発火することがあると報告されています。この順序になると、確定Enterが来た時点ですでに変換は終わったことになっていて isComposing が false、keyCode も通常の 13 になり、上のガードをすり抜けて誤送信し得ます。Safariの挙動はバージョンで変わることがあるので、実機で確かめるのが確実です。
これに備えるなら、compositionend で変換を確定した時刻を覚えておき、その直後に来たEnterを飲み込みます。確定してからほんの一瞬だけ「今のは確定のEnterかもしれない」と見なす、という考え方です。
// OK(本命): Safariの発火順にも耐える
var lastCompositionEnd = 0;
input.addEventListener('compositionend', function (e) {
lastCompositionEnd = e.timeStamp; // 変換を確定した時刻を記録
});
input.addEventListener('keydown', function (e) {
if (e.key !== 'Enter') return;
// 変換中、または確定した直後(ごく短い時間内)のEnterは無視する
if (e.isComposing || e.keyCode === 229 || e.timeStamp - lastCompositionEnd < 100) return;
runSearch();
});
Chromeなどでは isComposing だけで足りるので、確定時刻との差分チェックは主にSafari対策の保険です。compositionend の直後(ここでは100ミリ秒以内)に来たEnterを無視することで、Safariのように compositionend が確定Enterの keydown より先に発火してもすり抜けを防げます。この時間は、確定してからユーザーが意図的にもう一度Enterを押すまでの間隔より十分短く、かつ確定直後の1発を拾える値に、実機で確認して調整してください。
ブラウザ差:確定Enterのときに何が返るか
変換確定のEnterで keydown がどんな値になるかは、ブラウザによって差があります。おおまかな傾向は次のとおりです(挙動はバージョンで変わるため、重要な環境は実機で確認してください。特にSafari/iOS Safariは発火順が変わりやすい部分です)。
| ブラウザ | 確定Enterの isComposing |
keyCode |
備考 |
|---|---|---|---|
| Chrome/Edge | true | 229 | keydownは key='Process' で届き isComposing で弾ける。ただし keyup は key='Enter' で届く |
| Firefox | true | 229 | 同上 |
| Safari(macOS) | false になることがある | 13 | compositionend が先に発火し、簡易版だとすり抜けることがある。確定直後の除外を併用で対処 |
| iOS Safari | 実機で要確認 | — | 変換確定まわりの挙動が不安定とされる。確定直後の除外を併用が安全側 |
表は keydown の値です。keyup や暗黙送信の挙動も環境で分かれます。実際、手元のWindows Chromeで試したところ、MS-IMEでもGoogle日本語入力でも、確定Enterでは送信が起きませんでした(keydown/keyup とも key='Process'/229 として消費され、フォームの暗黙送信も走らない)。一方Safariなどでは、確定Enterが素のEnterとして届き誤爆するとされています。この差こそが「AIに聞いた対策が自分の環境で効かない」の正体です。上のデモのインスペクターで、まず自分のブラウザが返す値を見るのが遠回りに見えて一番確実です。
修正デモ:確定Enterを無視する
下は同じ検索フォームを、暗黙送信を e.preventDefault() で無効化し、検索を keydown で明示的にトリガーする形に直したものです(上の本命版のガード=isComposing・keyCode 229・確定直後の除外を適用)。日本語を打って変換確定のEnterを押しても検索は走らず、確定後にもう一度Enterを押したとき、または検索ボタンでだけ検索が実行されます。インスペクターで、確定Enterが「無視」と記録されるのを確認してください。
変換候補を確定するEnterは飲み込まれ、テキストが確定した状態で押したEnter(または検索ボタン)でだけ検索が実行されます。ユーザーは「確定したつもりが送信されていた」というストレスから解放されます。
補足:チャット入力の「Enterで送信/Shift+Enterで改行」
パターン3のチャット風入力も、判定の入れ方は同じです。送信の条件に「Shiftが押されていないこと」を足し、確定Enterを弾くガードを先頭に置きます。textarea のときは e.preventDefault() で改行の挿入を止める点だけ注意してください。
textarea.addEventListener('keydown', function (e) {
if (e.key !== 'Enter') return;
// 変換確定のEnterは無視(isComposing・keyCode 229・確定直後の除外)
if (e.isComposing || e.keyCode === 229 || e.timeStamp - lastCompositionEnd < 100) return;
if (e.shiftKey) return; // Shift+Enter は改行として通す
e.preventDefault(); // 素のEnterでの改行は止める
sendMessage();
});
「変換中の文字列で検索が走ってしまう」という、Enter送信ではなく input イベント側の別問題もあります(インクリメンタル検索が変換途中で発火するケース)。そちらは compositionstart/compositionend で入力処理を止める話になり、この記事とは対処が分かれます。検索ボックスの絞り込みで困っている場合は、そちらを扱う記事を別途用意する予定です。
まとめ
日本語の確定Enterでフォームが送信される問題は、確定のEnterが素のEnterとして扱われる環境で起きます。ここが分かりにくいのは、ブラウザとIMEの組み合わせで起きたり起きなかったりするためで、モダンなChromeでは確定Enterが消費されて再現しないことも多く、主にSafari(Mac/iOS)などで問題になります。ブラウザのバージョンで挙動が変わることもあります。
環境で起きたり起きなかったりするからこそ、Enterを処理する前に、変換確定のEnterかどうかを必ず確かめる。isComposing を第一に、確定直後の除外を保険で足す。
実装としては、暗黙送信に頼らず keydown で判定し、isComposing(+ keyCode 229)で変換中のEnterを弾き、compositionend の確定時刻を見て直後のEnterも飲み込む。判定は keyup/keypress ではなく keydown で行う。これで、いま再現していない環境も、Safariのように再現する環境も、まとめて安全にできます。自分の環境で実際にどうなるかは、上のデモのインスペクターで生の値を見て確かめてください。
関連するUI事例
Enter送信やバリデーションが絡む、コピペで動くフォームの事例です。