JavaScript | DOM 操作:イベント基礎 – キーボードイベント(keydown / keyup)

JavaScript
スポンサーリンク

キーボードイベントとは何か(keydown / keyup)

keydown は「キーが押された瞬間」、keyup は「キーが離された瞬間」に発火するイベントです。ショートカット、ゲームの操作、フォームの補助などで“押した/離した”のタイミングを正確に扱えます。ここが重要です:keydown はキーを押し続けていると連続で発火します(キーリピート)、keyup は一度だけです。押下中の連打を検知したいか、終了の合図だけ欲しいかで使い分けます。


基本の使い方(登録・判定・既定動作の制御)

ショートカットの基本(Ctrl/⌘+S で保存)

<script>
  function save() { console.log("保存しました"); }

  document.addEventListener("keydown", (e) => {
    const isSave = (e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "s";
    if (!isSave) return;
    e.preventDefault();        // ブラウザの「ページ保存」を抑止
    save();
  });
</script>
HTML

ここが重要です:ショートカットは e.key と修飾キー(ctrlKey, metaKey, shiftKey, altKey)で判定します。既定動作がある場合は preventDefault を忘れない。

フォーム操作の補助(Enter で送信、Esc でキャンセル)

<form id="f">
  <input id="q" placeholder="検索">
  <button>送信</button>
</form>
<script>
  f.addEventListener("keydown", (e) => {
    if (e.key === "Escape") { e.preventDefault(); q.value = ""; }
    if (e.key === "Enter")  { e.preventDefault(); console.log("検索:", q.value.trim()); }
  });
</script>
HTML

ここが重要です:Enter の既定動作(フォーム送信)をカスタム処理へ置き換えるときは preventDefault を使います。意図が明確な場面だけに限定しましょう。


キーの識別と詳細(key / code / repeat)

e.key と e.code の違い

<script>
  document.addEventListener("keydown", (e) => {
    console.log("key:", e.key, "code:", e.code);
  });
</script>
HTML

ここが重要です:e.key は「入力される文字や意味」(例: “a”, “A”, “Enter”)。e.code は「物理キーの位置」(例: “KeyA”, “ArrowLeft”)。英字のレイアウト差や IME の影響を受けず位置で判定したいなら e.code、文字・記号そのものを判定したいなら e.key を使います。

長押しの検知(repeat)

<script>
  document.addEventListener("keydown", (e) => {
    if (e.key === "ArrowRight") {
      if (e.repeat) { /* 長押し中の繰り返し */ }
      else { /* 最初の一発だけ */ }
    }
  });
</script>
HTML

ここが重要です:keydown は長押しで繰り返し発火します。初回だけ処理したいなら e.repeat を見て弾く、連続移動やオートスクロールなら repeat を活用します。


日本語入力(IME)との付き合い方(compositionstart / end)

未確定文字の扱いと Enter 判定の分離

<input id="name" placeholder="名前">
<script>
  let composing = false;

  name.addEventListener("compositionstart", () => composing = true);
  name.addEventListener("compositionend",   () => composing = false);

  name.addEventListener("keydown", (e) => {
    if (e.key === "Enter") {
      if (composing) return;        // IME確定の Enter はスルー
      e.preventDefault();
      console.log("確定入力:", name.value.trim());
    }
  });
</script>
HTML

ここが重要です:日本語入力中(composition)はキーイベントの意味が変わります。Enter・Backspace 等の処理は“確定後”に行うのが安全です。compositionend で状態を戻し、判定を分けましょう。


継続入力の設計(ゲーム・ナビゲーションでの押下/解放)

押下中のキー集合で制御する

<div id="box" style="width:120px;height:80px;background:#eee"></div>
<script>
  const pressed = new Set();

  document.addEventListener("keydown", (e) => {
    pressed.add(e.code);          // 物理キーで管理
  });
  document.addEventListener("keyup",   (e) => {
    pressed.delete(e.code);
  });

  // ループで状態に応じて動かす
  function tick() {
    if (pressed.has("ArrowRight")) box.style.transform = "translateX(5px)";
    if (pressed.has("ArrowLeft"))  box.style.transform = "translateX(-5px)";
    requestAnimationFrame(tick);
  }
  tick();
</script>
HTML

ここが重要です:押下中かどうかは keydown/keyup の“集合管理”が堅牢です。判定は e.code を使うとキーレイアウト差に強くなります。


スコープとアクセシビリティ(どこで受けるか、誰がフォーカスか)

document 全体で受ける vs 特定要素だけで受ける

<div id="pane" tabindex="0" style="border:1px solid #ccc">ここで矢印キーを受ける</div>
<script>
  pane.addEventListener("keydown", (e) => {
    if (e.key.startsWith("Arrow")) console.log("矢印:", e.key);
  });
  // pane をフォーカス可能にするため tabindex="0" を付ける
</script>
HTML

ここが重要です:キーボードイベントは“フォーカスのある要素”に届きます。特定の領域でだけ受けたいなら、tabindex を付けてフォーカス可能にし、その要素にリスナーを付けます。グローバルショートカットは document に付けますが、フォーム入力と衝突しないよう条件分岐を入れます。


よくある落とし穴と回避策

無闇な preventDefault

Enter・Backspace・Space などの既定動作を止めると、ユーザーの期待を裏切りやすいです。明確な意図がある場面のみに限定し、フォーカス状態や入力要素かどうかで条件分岐を入れてください。

key と code の取り違え

文字で判定すべき場面(”a”, “A”, “+”)と位置で判定すべき場面(矢印、ゲーム操作)を混同すると、配列が違うキーボードや IME で誤動作します。目的に合わせて選び分ける癖をつけましょう。

長押しの二重実行

keydown は連続発火します。初回だけ処理したい場合は e.repeat を弾くか、押下状態を Set で管理して“押されたときだけ”の分岐を作ると安定します。

フォーカスの抜け

想定外の要素がフォーカスされているとショートカットが効かないことがあります。必要領域に tabindex を付ける、初期表示で focus() を適切に呼ぶ、document で受ける場合は入力要素のときはスキップするなど、フォーカス設計を意識します。


実践例(検索ボックス、ショートカット、編集モード)

検索ボックス:Enterで検索、Escでクリア(IME対応)

<input id="q" placeholder="検索">
<script>
  let composing = false;
  q.addEventListener("compositionstart", () => composing = true);
  q.addEventListener("compositionend",   () => composing = false);

  q.addEventListener("keydown", (e) => {
    if (e.key === "Escape") { e.preventDefault(); q.value = ""; }
    if (e.key === "Enter" && !composing) {
      e.preventDefault();
      console.log("検索:", q.value.trim());
    }
  });
</script>
HTML

アプリショートカット:Ctrl/⌘+K でコマンドパレット

<div id="palette" hidden>コマンド...</div>
<script>
  document.addEventListener("keydown", (e) => {
    const hotkey = (e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k";
    if (!hotkey) return;
    e.preventDefault();
    palette.hidden = !palette.hidden;
  });
</script>
HTML

編集モード:Enterで確定、Shift+Enter で改行

<textarea id="note" rows="4"></textarea>
<script>
  note.addEventListener("keydown", (e) => {
    if (e.key === "Enter" && !e.shiftKey) {
      e.preventDefault();
      console.log("確定:", note.value.trim());
    }
    // Shift+Enter は既定動作(改行)を許可
  });
</script>
HTML

まとめ

keydown は押下、keyup は解放。keydown は長押しで繰り返し発火し、keyup は一度だけ。キー判定は e.key(意味)と e.code(位置)を目的で使い分け、修飾キーでショートカットを組む。既定動作の制御は preventDefault を必要最小限にし、日本語入力は composition の状態を考慮して Enter 等の判定を分ける。押下中管理は Set+keydown/keyup が堅牢。フォーカスとスコープを正しく設計すれば、初心者でも意図通りで滑らかなキーボードインタラクションを作れます。

タイトルとURLをコピーしました