フォームの Enter 送信で isComposing と keyCode === 229 を併用する理由
IME の変換確定に使う Enter キーで、書きかけのメッセージが送信されることがあります。isComposing と非推奨の keyCode === 229 を併用して、変換中の Enter を送信処理から除外する理由を解説します。
チャット UI などで Enter キーを押したらメッセージを送信する機能を実装するとき、IME を利用するユーザーへの配慮が必要です。日本語や中国語などの入力では、文字を組み立てるために変換候補を選んでから Enter キーで確定します。単純に event.key === "Enter" だけで送信すると、ユーザーは文字を確定したつもりなのに、書きかけのメッセージが送られてしまいます。
変換を確定するつもりで押した Enter キーによって、書きかけのメッセージがそのまま送信されてしまっています。
この問題への対策として、次のようなコードを見かけることがあります。
input.addEventListener("keydown", (event) => {
if (event.isComposing || event.keyCode === 229) return;
if (event.key === "Enter") {
// 送信処理...
}
});isComposing は、IME で文字を組み立てている間に発生するイベントかどうかを判定するプロパティです。keyCode === 229 は、IME が処理したキー入力で使われてきた値です。どちらも変換中の Enter キーを送信処理から除外するために使われます。
isComposing だけで IME の変換中かどうかを表せるのに、なぜ keyCode も確認するのでしょうか。さらに言えば keyCode は非推奨の API です。この記事では、フォームやチャットの Enter 送信を題材に、2 つの判定を組み合わせる理由と実装時の注意点を説明します。
Enter キーは送信だけでなく変換確定にも使われる
IME(Input Method Editor)は、キー入力を組み合わせて日本語などの文字を入力する仕組みです。たとえば「にほんご」と入力し、「日本語」という候補を選び、Enter キーで確定します。
ブラウザは、このような文字の組み立てを composition セッションとして扱います。UI Events 仕様では、次のイベントが定義されています。
compositionstart: 文字の組み立てが始まるcompositionupdate: 組み立て中の文字列が更新されるcompositionend: 組み立てが完了、またはキャンセルされる
キーボードイベントの KeyboardEvent.isComposing は、そのイベントが compositionstart と対応する compositionend の間に発生したかどうかを表します。
そのため、独自の Enter 送信ではまず isComposing を確認し、文字を組み立てている間は送信処理へ進まないようにします。
input.addEventListener("keydown", (event) => {
if (event.isComposing) return; // 変換中は送信しない
if (event.key === "Enter") {
// 送信処理...
}
});ただし、ブラウザの実装によっては、IME の変換確定に使う Enter キーが isComposing: false で通知されるという問題が報告されています。
isComposing だけでは確定用の Enter を見分けられない環境がある
仕様が示すイベント順序では、composition セッションを終了させる
keydown は isComposing: true で通知され、その後に compositionend が発生するとされています。つまり、IME が変換を確定する Enter キーは、isComposing: true の keydown として受け取れるはずです。
一方、WebKit の不具合報告 #165004には、Safari が確定用の
keydown より先に compositionend を送っていたことが記録されています。この場合、確定用の Enter を受け取った時点でセッションが終わっているため、isComposing は false になってしまうのです。

そのため、Safari では isComposing だけでは確定用の Enter を見分けられず、書きかけのメッセージが送信されてしまうことがあったのです。
ここで isComposing に頼らず、compositionstart・compositionend イベントでフラグを更新し、変換中かどうかを自分で管理すればよいのではないかと考えるかもしれません。
let composing = false;
input.addEventListener("compositionstart", () => {
composing = true;
});
input.addEventListener("compositionend", () => {
composing = false;
});
input.addEventListener("keydown", (event) => {
if (composing) return; // 変換中は送信しない
if (event.key === "Enter") {
// 送信処理...
}
});しかし、この方法でも同じ問題が起こります。フラグを false にする compositionend イベントが確定用の keydown より先に発生してしまうため、keydown を受け取った時点ではフラグがすでに変更されてしまっているからです。問題の原因はイベントの順序そのものにあるので、composition イベントを自分で管理しても回避できないのです。
keyCode === 229 を併用する
keyCode === 229 は、IME が処理するキー入力で使われてきた値です。2010 年の W3C の提案資料には、IME がキー入力を処理している
keydown では 229 を返すという計算手順が書かれています。また、Windows の仮想キーコードでは VK_PROCESSKEY が 0xE5(10 進数で 229)として定義されています。
ただし、現在の UI Events 仕様の legacy keyboard eventsでは、
keyCode はシステムや実装に依存する値として扱われています。正式に規定されないまま各ブラウザが独自に実装してきた経緯があり、仕様はその実態を非規範的に記述するにとどめているためです。
Safari の問題でこの値が役立つのは、isComposing が false になっていても、確定用の keydown の keyCode が 229 なら独自の送信処理を止められるからです。この回避策は、先ほどの WebKit の不具合報告でも説明されています。
そのため、以下のように isComposing と keyCode === 229 を併用するコードが推奨されているのです。
if (event.isComposing || event.keyCode === 229) {
return;
}以下のデモを Safari で実行してみると、isComposing と event.keyCode の値がそれぞれどのように変化するかを確認できます。
Safari のイベント順序の問題には、すでに WebKit 側の修正があります。WebKit Bug #311717。そのため Safari のバージョンによっては既に修正が取り込まれている場合があります。

フォームの暗黙的送信を使えば IME の判定をブラウザに任せられる
ここまでは、keydown で Enter キーを検知して送信する場合の対策を説明しました。しかし、通常のフォームであれば、そもそも Enter キーを JavaScript で検知する必要はありません。ブラウザが備える暗黙的送信(implicit submission)を利用できます。
暗黙的送信とは、テキスト入力欄で Enter キーを押すなどの操作によって、送信ボタンを直接クリックしなくてもフォームを送信する仕組みです。HTML 仕様では、フォームの中で最初に現れる送信ボタンをデフォルトボタン(default button)と呼び、暗黙的送信に対応するブラウザは、そのボタンが無効化されていなければ
click イベントを発生させるとされています。送信処理は通常の submit イベントで受け取ります。
たとえば、単一行の入力欄と送信ボタンを持つフォームなら、次のように実装できます。<form> 要素で入力要素を囲み、type="submit" のボタンを配置してフォームとして扱うのがポイントです。
<form id="message-form">
<label for="message">メッセージ</label>
<input id="message" name="message" type="text" required />
<button type="submit">送信</button>
</form>
<p id="result" role="status"></p>
<script>
const form = document.querySelector("#message-form");
const result = document.querySelector("#result");
form.addEventListener("submit", (event) => {
event.preventDefault();
const data = new FormData(form);
result.textContent = `送信内容: ${data.get("message")}`;
});
</script>このコードには、keydown のリスナーも isComposing や keyCode の判定もありません。変換確定の Enter を IME の操作として扱うか、フォームの送信として扱うかはブラウザに任せ、アプリケーションは submit イベントが発生したときだけ処理します。独自のキーイベント処理が変換確定を送信と誤認する原因そのものをなくせるのです。
ただし、textarea の Enter は通常は改行として扱われます。チャット UI のように「Enter で送信、Shift+Enter で改行」という独自の操作を実装する場合は、暗黙的送信だけでは実現できないため、ここまで説明した IME の判定が必要になります。まずは標準のフォーム操作で要件を満たせるかを検討し、独自の Enter 送信が必要な場合にキーイベントを扱うとよいでしょう。
まとめ
- Enter キーは IME の変換確定にも使われるため、独自の Enter 送信では文字の組み立て中かどうかを確認する
isComposingは composition セッション中のキーボードイベントかどうかを表し、IME 変換中の判定に使える- Safari では確定用の
keydownより先にcompositionendが発生する問題が報告されており、isComposingがfalseになることがある - 上記の問題を回避するため、
keyCode === 229も併用して IME が処理するキー入力かどうかを判定する - 通常のフォームでは暗黙的送信と
submitイベントを利用することで、独自の Enter 判定や IME の状態管理をブラウザに任せられる
参考
UI Events — Composition Events
UI Events — Legacy keyboard event attributes
WebKit Bug 165004 — The event order of keydown/keyup events and composition events are wrong on macOS
WebKit Bug 311717 — Fix a regression and turn on correct composition event ordering by default
modern-web-guidance/skills/modern-web-guidance/guides/forms/ime-safe-enter-submit.md at v0.0.186 · GoogleChrome/modern-web-guidance
HTML Standard — Implicit submission




