Windows 10 の Microsoft Edge で「電卓の入力欄に数値を入れて Enter を押すとフォーカスが外れて以後入力できない」という相談が少なくありません。この記事では原因の正体(暗黙的フォーム送信)を、実務で使える回避策・実装例・テスト観点まで徹底解説します。Chrome/Firefox では再現しない理由も腑に落ちるはずです。
Edge で Enter を押すとフォーカスが外れる現象の正体
発生条件はシンプルです。<form> の中にある <input>(特に type="text" など)にフォーカスした状態で Enter キーを押すと、Edge は「暗黙的なフォーム送信(implicit submission)」を開始します。ページ遷移や XHR を行っていなくても、送信フェーズに入った時点でブラウザはフォーカスを入力欄から外すことがあり、結果として「入力欄が使えなくなった」ように見えます。
この暗黙送信は「送信ボタンが表示されていなくても」「<button> の type を明示していなくても」発火し得ます。Chrome や Firefox ではフォーカスの扱いが比較的寛容で、そのまま入力欄に残ることもありますが、Edge は送信処理のライフサイクル中にフォーカス管理が厳格なため、差異が表面化します。
「keypress で event.preventDefault() を呼んだが効かなかった」という声があるのは、keypress 自体が非推奨であることに加え、暗黙送信は keydown の段階で抑止しないと間に合わないケースがあるためです。
最短で解決する 4 つの回避策(用途別に選ぶ)
電卓やタイマーなどの「サーバーに送る必要がない UI」では、根本的にフォームを使わないのが最もシンプルで堅牢です。とはいえ、既存実装やバリデーションの都合でフォームを残したいケースもあるでしょう。以下のいずれかで解決できます。
| 方法 | 概要 | 利点 | 注意点 | 実装スニペット(抜粋) |
|---|---|---|---|---|
| フォームを使わない(推奨) | 暗黙送信の入口をそもそも無くす。UI は <div> などでラップ。 | 副作用が最小。ブラウザ差異の影響を受けにくい。 | 既存のフォームバリデーションが使えない。送信が必要な画面には不向き。 | <div class="calculator">...</div> |
| Enter を keydown で無効化 | 入力欄で Enter を押しても既定動作(送信)をキャンセル。 | 現状の DOM 構造を大きく変えずに導入できる。 | IME 変換確定の Enter まで潰さないよう isComposing に配慮。 | <input onkeydown="if(!event.isComposing && event.key==='Enter'){event.preventDefault();}"> |
ボタンの型を type="button" に | デフォルト(省略時)は submit。これを明示的に非送信用へ。 | 暗黙送信のトリガー(送信ボタン)を無効化できる。 | フォーム内に 1 つでも submit が残ると効果が薄い。 | <button type="button">=</button> |
| 送信自体をブロック | submit イベントで preventDefault()。 | フォームの HTML は維持、バリデーションも活かしやすい。 | フォーカス維持は keydown 併用が堅実。 | <form onsubmit="return false;">...</form> |
実際の解決例:相談元のケースでは、<form> ラッパーを外して単なる <div> に置き換えたところ、Edge でも Enter 後にフォーカスが外れなくなり、問題は解消しました。
なぜ Enter で「暗黙送信」が起きるのか(しくみを理解する)
- 暗黙送信の条件:フォーム内でテキスト入力中に Enter を押すと、送信ボタンが無くてもブラウザは送信相当のアクションを試みます。これは HTML 標準の振る舞いです。
- フォーカス喪失の理由:送信フェーズに入ると、ブラウザは検証・エラーバブル・ボタン活性化などの内部処理を行います。この過程でアクティブ要素がフォームやボタン、あるいはドキュメントに移り、入力欄から外れます。
- ブラウザ差:Chrome/Firefox は「送信がキャンセルされた場合にフォーカスを戻す」実装が比較的多い一方、Edge では戻らない場面があり、差異として現れます。アプリ側で積極的に既定動作を抑止すれば、この差は吸収できます。
堅牢な実装パターン(コピペで使える)
パターン A:フォームを使わない電卓
<!-- HTML -->
+=
パターン B:フォームを残しつつ Enter を抑止(keydown)
<form id="calcForm" autocomplete="off">
=
パターン C:ボタンの型を明示して暗黙送信のトリガーを消す
<button type="button" id="equals">=</button>
パターン D:onsubmit="return false;" を使う(最低限)
既存テンプレートの改修コストを最小にしたいときの応急処置です。Enter で送信されても必ずキャンセルされます。ただしフォーカスの戻しはブラウザ任せになるため、keydown での抑止と併用を推奨します。
<form onsubmit="return false;">...</form>
Edge(Chromium 版を含む)での小さな落とし穴
- keypress は非推奨:フォーカスや IME 周りのタイミング差で取りこぼしが生じます。keydown を使ってください。
- IME 確定 Enter:日本語入力中の Enter を一律で潰すと UX が悪化します。
event.isComposingのチェックを必ず入れましょう。 - Numpad の Enter:多くの環境で
event.keyはどちらも “Enter” を返します。keyCode===13に頼る実装は互換性が低下するため避けます。 - input type=”number”:ブラウザによって IME 可否やスクロールで値が変わる挙動が異なります。電卓用途では
type="text"+inputmode="decimal"の方が扱いやすいことが多いです。
実装チェックリスト(Edge 向けに追加確認)
| 観点 | 確認内容 | 方法 |
|---|---|---|
| 暗黙送信の抑止 | Enter で submit が発火しない | DevTools の「Event Listener Breakpoints」で Submit を監視 |
| フォーカス保持 | 計算後も入力欄にキャレットが残る | document.activeElement をログ出力して確認 |
| IME 配慮 | 変換確定 Enter をブロックしていない | event.isComposing の分岐テスト |
| アクセシビリティ | ボタン操作や読み上げの文脈が適切 | aria-label / aria-live の設定 |
| 回帰防止 | 将来の仕様差にも耐える | E2E テストで Enter 操作を自動化 |
Vanilla JS:最小再現と完全解決の対比
NG(問題が起きる最小例)
<form>
OK(keydown 抑止)
<form id="okForm">
React 実装例:Enter だけで計算し、送信はさせない
import React, { useRef } from 'react';
export function Calculator() {
const inputRef = useRef(null);
const onKeyDown = (e) => {
// IME 変換中の Enter は通す
if (e.nativeEvent.isComposing) return;
if (e.key === 'Enter') {
e.preventDefault();
// 計算処理…
// フォーカスを維持
requestAnimationFrame(() => inputRef.current?.focus());
}
};
return (
e.preventDefault()} autoComplete="off">
{/* 計算 */}}>=
);
}
TypeScript 実装例:型安全に IME と Enter を扱う
const input = document.querySelector<HTMLInputElement>('#tsInput')!;
input.addEventListener('keydown', (e: KeyboardEvent) => {
// isComposing は KeyboardEvent の一部(Edge/Chromium でサポート)
if ((e as any).isComposing) return;
if (e.key === 'Enter') {
e.preventDefault();
// 計算ロジックへ委譲
calculate();
// フォーカス維持
requestAnimationFrame(() => input.focus());
}
});
フォーカス設計のベストプラクティス
- 計算後は再フォーカス:操作のループを素早く回せます。
focus()だけでなくselect()で全選択にすると、次の数値を上書き入力しやすくなります。 - 等価操作キー:Enter のほかに = や Space で計算ボタンを押せると、ユーザーの学習コストが下がります。
- スクリーンリーダー対応:結果表示に
aria-live="polite"を使い、計算結果が読み上げられるようにします。 - フォームが必要な画面は送信を明示:本当に送信させたい場面では
<button type="submit">を明示し、Enter による送信を仕様として取り込むのが自然です。
パフォーマンスと堅牢性のメモ
- イベントの登録場所:個々の入力に直接
onkeydownを書くより、イベント委任(親でaddEventListener)の方がスケーラブルです。 - 再フォーカスのタイミング:
requestAnimationFrameを使うと、DOM 更新後に確実に戻せます。同期的にfocus()すると、ブラウザの内部処理と競合することがあります。 - バリデーション:フォームを使わない構成では、正規表現・
ConstraintValidationAPI・カスタムメッセージなどを明示的に実装しましょう。
E2E テスト例(Playwright)で回帰を防ぐ
import { test, expect } from '@playwright/test';
test('Edge でも Enter でフォーカスが外れない', async ({ page }) => {
await page.goto('/calculator');
const input = page.locator('#display');
await input.click();
await input.type('123');
await page.keyboard.press('Enter');
await expect(input).toBeFocused(); // ここが通れば回避策が効いている
});
FAQ(よくある質問)
Q. preventDefault() を書いたのに効きません。
A. keydown で呼んでいるかを確認してください。keypress や keyup では間に合わないことがあります。また、フォームに残った type="submit" のボタンが暗黙送信を引き起こす場合もあります。
Q. IME の Enter まで無効になってしまいます。
A. event.isComposing を使い、変換中(合成中)は既定動作を通すようにします。これは Edge でも利用できます。
Q. フォームを外せない事情があります。
A. onsubmit="return false;" で送信をブロックしつつ、入力欄の keydown で Enter を抑止する二段構えにしてください。加えて各ボタンは type="button" を明記します。
まとめ:Edge 固有の「仕様差」を前提に設計する
Edge では Enter が暗黙送信を誘発し、送信フェーズ中にフォーカスが外れることがあります。電卓のような UI では次のいずれかで確実に回避できます。
- フォームを使わない構成にする(最も安全)
- 入力欄の keydown で Enter を抑止(
isComposingに配慮) - ボタンの型を
type="button"に明示 - フォームの
submitをキャンセル(onsubmit="return false;"またはaddEventListener)
加えて、計算後の再フォーカスや読み上げ対応を行えば、Edge でも軽快でアクセシブルな操作感を提供できます。小さな実装差が UX 全体を左右する領域なので、Enter キー周りは最初から「仕様差を吸収する」前提で組み立てておくと安心です。

コメント