
JavaScriptキーボードイベント完全解説:key、code、keyCodeの違いとデバッグ方法
📷 Life Of Pix / PexelsJavaScriptキーボードイベント完全解説:key、code、keyCodeの違いとデバッグ方法
JavaScriptキーボードイベントの実践ガイド — keyCodeが非推奨になった理由、keyとcodeの違い、あらゆるブラウザとキーボードレイアウトで動作するショートカットの作り方を解説します。
同じようなバグに何度も遭遇したことがあります。Chromeでキーボードショートカットハンドラを書いて、ちゃんと動くことを確認してリリースしたところ、しばらくしてFirefoxで動かないというバグ報告が届きました。同じコードなのに動作が違う。一時間ほど調べてみると原因は単純でした — キーの検出にe.keyCodeを使っていて、ブラウザとキーボードレイアウトによって異なる値を返していたのです。
このバグは完全に防げます。だからこそ、JavaScriptのキーボードイベントを正しく理解することが重要です。この記事では、キーボードイベントモデル全体を解説します — どのイベントが発生するか、どのプロパティを使うべきか、何を避けるべきか、そしてどこでも動作するショートカットの作り方まで。
3つのイベント:keydown、keypress、keyup
ユーザーがキーを押すと、ブラウザは順番に最大3つのイベントを発生させます:
- keydown — キーを押した瞬間に発生、文字が挿入される前
- keypress — keydownの後に発生、文字を生成するキーのみ(文字、数字、記号)
- keyup — キーを離したときに発生
簡潔に言うと:ほとんどの場合はkeydownを使ってください。理由はこうです:
keypressは非推奨です。Escape、Delete、F1〜F12、矢印キーなどの非印字キーでは発生しませんでした。ショートカットハンドラが文字キーでしか動かなかったなら、keypressが原因だった可能性があります。新しいコードでは使わないでください。
keyupはキーを離した後に反応したい場合に便利です — 例えばユーザーが文字を入力した後にプレビューを更新するとき。ただしショートカットやゲームコントロールにはkeydownの方が速く予測可能です。
keydownはキーを押し続けると OSのキーリピート速度で繰り返し発生するという利点もあります。スクロールやゲームキャラクターの移動に必要な動作です。
document.addEventListener('keydown', (e) => {
// ここが正しい場所です
console.log(e.key, e.code);
});
重要なプロパティの解説
KeyboardEventが発生すると、イベントオブジェクトには多くのプロパティが含まれます。知っておくべきものを説明します。
key — キーが生成する値
keyは現在のコンテキストでキーが表す文字列値を返します。通常の文字キーなら文字そのもの:"a"、"A"(Shift付き)、"é"(フランス語キーボード)。特殊キーなら説明的な名前:"Enter"、"Escape"、"ArrowLeft"、"F5"、"Backspace"。
まず最初に使うべきプロパティです。レイアウトと修飾キーを認識し、キーがユーザーにとって何を意味するかを教えてくれます。
document.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
submitForm();
}
if (e.key === 'Escape') {
closeModal();
}
if (e.key === 'ArrowLeft') {
goToPreviousSlide();
}
});
注意点:keyは大文字と小文字、修飾キーを区別します。Shiftなしでaキーを押すと"a"、Shift付きだと"A"です。大文字小文字を区別しないショートカットを作るには正規化します:
if (e.key.toLowerCase() === 'k') {
openCommandPalette();
}
code — どの物理キーが押されたか
codeは修飾キーやキーボードレイアウトに関わらず、物理的なキー位置の識別子を返します。文字エリアの左上のキーは常に"KeyQ"です — ユーザーのレイアウトがそこに"A"を配置していても同じです(AZERTYレイアウトのように)。
形式は一貫しています:文字キーは"KeyA"〜"KeyZ"、数字キーは"Digit0"〜"Digit9"、ファンクションキーは"F1"〜"F12"。
キーが生成する文字より位置が重要な場合にcodeを使います。典型的な使用例がゲームコントロールです:
document.addEventListener('keydown', (e) => {
switch (e.code) {
case 'KeyW':
case 'ArrowUp':
player.moveUp();
break;
case 'KeyS':
case 'ArrowDown':
player.moveDown();
break;
case 'KeyA':
case 'ArrowLeft':
player.moveLeft();
break;
case 'KeyD':
case 'ArrowRight':
player.moveRight();
break;
}
});
codeを使うことで、QWERTY、AZERTY、DvorakどのレイアウトでもWASDコントロールが同じように動作します。AZERTYではWキーが別の位置にあるためe.keyは"z"を返しますが、e.codeは物理的な位置が同じなので"KeyW"を返します。
keyCodeとwhich — レガシー、非推奨、避けてください
keyCodeとwhichは古い方法です。キーの数値コードを返します — Aは65、Enterは13、Escapeは27。問題は特に記号や特殊キーでこれらの値が一貫していなかったことです。ブラウザによって異なる数値を返し、値はWindowsの仮想キーコードをベースにした直感的でないものでした。
MDNのドキュメントはkeyCodeとwhichの両方を非推奨としてマークしています。後方互換性のためまだ存在しますが、新しいコードでは使わないでください。
古いコードでよく見るもの:
// やめてください
if (e.keyCode === 13) { /* Enter */ }
if (e.which === 27) { /* Escape */ }
現代的な代替:
// こうしてください
if (e.key === 'Enter') { /* Enter */ }
if (e.key === 'Escape') { /* Escape */ }
charCode — keypressのみ、これも非推奨
charCodeは文字のUnicodeコードポイントを返しましたが、keypressイベントでのみ、しかも印字可能な文字のみで動作しました。keypress自体が非推奨なのでcharCodeも二重に非推奨です。e.keyを使って、Unicode値が必要なら.codePointAt(0)を呼び出してください。
location — 複数箇所にあるキーの区別
locationはキーボードの複数の位置に存在するキーがどこで押されたかを教えます。数値です:0は標準位置、1は左側の修飾キー、2は右側の修飾キー、3はテンキー。
e.location === 1でe.key === "Shift"なら左Shiftキーが押されました。e.location === 3でe.key === "5"ならテンキーの5が押されました。めったに必要ありませんが、存在を知っておくと便利です。
修飾キー:Ctrl、Shift、Alt、Meta
キーボードショートカットでは通常、普通のキーと一つ以上の修飾キーを組み合わせます。イベントオブジェクトには各修飾キーのブール値プロパティがあります:
e.ctrlKey— Ctrlキーが押されているe.shiftKey— Shiftキーが押されているe.altKey— Alt(MacではOption)キーが押されているe.metaKey— Metaキーが押されている(MacではCommand、WindowsではWindowsキー)
document.addEventListener('keydown', (e) => {
// Ctrl+S(MacではCmd+S)
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault(); // ブラウザの保存ダイアログを防ぐ
saveDocument();
}
// Ctrl+Shift+P — コマンドパレット
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'p') {
e.preventDefault();
openCommandPalette();
}
// Alt+Left — 戻る
if (e.altKey && e.key === 'ArrowLeft') {
goBack();
}
});
e.ctrlKey || e.metaKeyのパターンはクロスプラットフォームショートカットの標準的なアプローチです。macOSではほとんどのショートカットにCommand(metaKey)を使い、Windows/LinuxではCtrlを使います。このパターンで両方に対応します。
国際キーボードの問題
開発者がよくはまるシナリオがあります:アプリにキーボードショートカット(例:コメントのトグルにCtrl+/)を追加します。en-USキーボードでテストすると完璧に動きます。するとドイツのユーザーからショートカットが動かないというバグ報告が来ます。
ドイツ語キーボードレイアウトには/文字のための専用キーがありません。Shift+7などの別の組み合わせで入力します。そのキー位置を押したときのe.keyは期待したものとは全く違う値になります。
二つのアプローチがあります:
方法1:位置ベースのショートカットにはe.codeを使う
// USキーボードの/キーは特定の物理的位置にあります
// codeはその位置に対して常に'Slash'を返します
if (e.ctrlKey && e.code === 'Slash') {
toggleComment();
}
方法2:ユーザーが独自のショートカットを設定できるようにする
VS Codeがうまく実装しているアプローチです — 任意のショートカットをお好みのキー組み合わせに再割り当てできます。実装は複雑ですが、国際的なユーザーベースを持つプロフェッショナルツールの正解です。
アクセシビリティ:キーボードナビゲーションは重要です
アプリにカスタムキーボードインタラクションを追加する際、キーボードナビゲーションがコアなアクセシビリティ要件であることを忘れないでください。スクリーンリーダーに依存したりマウスを使えないユーザーはキーボードアクセスに頼っています。
いくつかのポイント:
フォーカス管理。 インタラクティブ要素がフォーカス可能かどうかを確認し(必要に応じてtabindex="0")、UIの変化に伴いフォーカスが論理的に移動することを確認します。モーダルを開いたらフォーカスをモーダル内に移し、閉じたらモーダルを開いた要素に戻します。
フォーカスをトラップしないでください(モーダルを除く)。ユーザーがTabキーでインターフェースを移動するときに詰まらないようにします。
キーボードショートカットだけに頼らないでください。 ユーザーが発見できない可能性があります。すべてのアクションに見えるUI代替を提供してください。
Keycode Viewerでデバッグする
キーボードインタラクションを実装するとき、最も難しい部分の一つは特定のキー押下に対してブラウザがどんな値を返すかを把握することです。ドキュメントは参考になりますが、実際にキーを押して結果を確認したいときがあります。
Keycode Viewerツールがまさにその役割を果たします。ツールを開いて任意のキーを押すと、即座に確認できます:
key— 論理的な値code— 物理的な位置識別子keyCode— レガシーの数値(既存コードの参照用)which— もう一つのレガシープロパティcharCode— keypressイベントの文字コードlocation— 標準/左/右/テンキーctrlKey、shiftKey、altKey、metaKey— 修飾キーの状態
国際キーボードや特殊文字を扱うとき、または既存コードが特定のキーを認識しない理由を調べるときに特に役立ちます。console.logを追加してリロードする代わりに、ツールを開いてキーを押すだけです。
クイックリファレンス
| プロパティ | 使用場面 | 非推奨? |
|---|---|---|
key | 文字または動作名 | いいえ |
code | 物理的なキー位置 | いいえ |
keyCode | なし — レガシーのみ | はい |
which | なし — レガシーのみ | はい |
charCode | なし — レガシーのみ | はい |
ctrlKey | Ctrl押下の確認 | いいえ |
shiftKey | Shift押下の確認 | いいえ |
altKey | Alt/Option押下の確認 | いいえ |
metaKey | Cmd/Winキー押下の確認 | いいえ |
基本的な考え方:まずe.keyを使ってください。キーの意味を教えてくれます。物理的な位置が重要な場合のみ(ゲームコントロール、レイアウト非依存のショートカット)e.codeを使います。新しいコードではkeyCode、which、charCodeを絶対に使わないでください。
これを正しく理解すれば、ブラウザ、キーボードレイアウト、OSを横断して一貫したキーボードインタラクションを作れます。そしてFirefoxユーザーからのバグ報告も届かなくなるでしょう。