ゲームのメニューをキーボードだけで操作できるようにする
遊びをつくる執筆にClaudeを使用しています。

ゲームを公開したあと、「マウスを使わずに操作したい」という声が届いたことはないでしょうか。キーボードだけでメニューを操作できるかどうかは、プレイしやすさに直結します。特にブラウザゲームでは、PCユーザーがキーボードのTabキーやEnterキーで素早く動かすことを好む場合が少なくありません。 キーボード操作への対応は、アクセシビリティの観点でも重要です。マウス操作が難しいユーザー、ゲームパッドをキーボードにマッピングしているユーザーにとって、キーボードだけで完結するメニューは「遊べるか遊べないか」の境界線になることがあります。 この記事では、HTMLとJavaScriptで作ったブラウザゲームのメニューを対象に、キーボード操作を実装するための考え方・手順・よくある失敗をまとめます。フレームワークは問わず、素のDOM操作を前提として説明しますので、どんな構成のゲームにも応用できます。
この記事の目次
まずフォーカス管理を理解する
キーボード操作の核心は「フォーカス」です。フォーカスとは、現在キーボード入力を受け付けている要素のことです。ブラウザはTabキーを押すたびにフォーカスを次の要素へ移し、EnterキーまたはSpaceキーでその要素をアクティブにします。
<button>や<a>はデフォルトでフォーカスを受け取れますが、<div>や<span>で作ったカスタムボタンは受け取れません。メニュー項目を<div>で実装している場合、tabindex="0"属性を追加するだけでフォーカス対象になります。
<div class="menu-item" tabindex="0" role="button">はじめから</div>role="button"はスクリーンリーダー向けの意味付けです。視覚だけでなくアシスト技術にも「これはボタンだ」と伝えられます。ただしroleを付けただけでは動作は変わらないため、後述するキーイベントの処理は別途必要です。
矢印キーでの選択移動を実装する
TabキーとEnterキーだけでも基本的な操作は成り立ちますが、ゲームのメニューらしい操作感を出すには、矢印キーで項目を移動できるようにするのが一般的です。この仕組みを「ローバーtabindex」と呼びます。
考え方はシンプルです。
- メニュー項目をリストとして管理し、現在選択中のインデックスを変数で持ちます。
- keydownイベントでArrowDown・ArrowUpを検知し、インデックスを増減させます。
- インデックスが変わるたびに、全項目のtabindexを-1にリセットしてから、選択中の項目だけtabindex="0"に戻し、focus()を呼びます。
let currentIndex = 0;
const items = document.querySelectorAll('.menu-item');
function moveFocus(newIndex) {
items[currentIndex].tabIndex = -1;
currentIndex = (newIndex + items.length) % items.length;
items[currentIndex].tabIndex = 0;
items[currentIndex].focus();
}
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowDown') moveFocus(currentIndex + 1);
if (e.key === 'ArrowUp') moveFocus(currentIndex - 1);
if (e.key === 'Enter' || e.key === ' ') items[currentIndex].click();
});(newIndex + items.length) % items.lengthという計算は、リストの末尾から先頭へ・先頭から末尾へ折り返すための書き方です。メニューが循環するため、端で止まるより自然な操作感になります。
Enterキーで.click()を呼ぶのは、既存のクリックハンドラをそのまま再利用するためです。マウス用とキーボード用で別々にロジックを書く必要がなくなります。
フォーカストラップ:メニューが開いている間だけ閉じ込める
ゲームでは「設定画面を開いた→Tabを連打したらゲーム本体のボタンにフォーカスが移ってしまった」という問題がよく起きます。これを防ぐのがフォーカストラップです。
フォーカストラップは、フォーカスを特定のコンテナ内に閉じ込める処理です。実装の要点は二つあります。
- コンテナ内のフォーカス可能な要素を全て取得し、最初と最後の要素を把握しておきます。
- 最後の要素でTabを押したら最初の要素へ、最初の要素でShift+Tabを押したら最後の要素へ強制移動します。
function trapFocus(container) {
const focusable = container.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const first = focusable[0];
const last = focusable[focusable.length - 1];
container.addEventListener('keydown', (e) => {
if (e.key !== 'Tab') return;
if (e.shiftKey) {
if (document.activeElement === first) { e.preventDefault(); last.focus(); }
} else {
if (document.activeElement === last) { e.preventDefault(); first.focus(); }
}
});
}メニューを開くときにtrapFocus(menuElement)を呼び、メニューを閉じるときはイベントリスナーをremoveEventListenerで取り外すか、AbortControllerで管理すると後始末が楽になります。
フォーカストラップはモーダルダイアログや一時的なオーバーレイメニューで特に重要です。ゲームのタイトル画面のような「常に表示されているメニュー」では必須ではありませんが、ポーズ画面や設定パネルには必ず実装することを検討してください。
フォーカスの見た目を消さない
実装上、見落とされやすい失敗がフォーカスリングの非表示化です。デザイン上「青い枠が邪魔」という理由でoutline: noneをCSS全体に当てているプロジェクトをよく見かけます。これをやるとキーボードユーザーは現在どの項目を選んでいるか分からなくなります。
対策は二つあります。一つは、outline: noneを使うかわりに独自のフォーカススタイルを定義することです。
.menu-item:focus {
outline: 2px solid #ffcc00;
outline-offset: 2px;
}もう一つは、:focus-visible疑似クラスを使うことです。マウスクリックのときはフォーカスリングを表示せず、キーボード操作のときだけ表示するという制御がCSSだけで書けます。
.menu-item:focus { outline: none; }
.menu-item:focus-visible { outline: 2px solid #ffcc00; }ゲームの配色に合わせた目立つ色を使うと、フォーカスリングがUIの一部として自然に溶け込みます。白背景のゲームなら濃い色、暗い背景なら明るい色を選ぶと視認性が上がります。
Escapeキーでメニューを閉じる
キーボードユーザーが特に期待している操作の一つが、Escapeキーによるキャンセルです。設定画面を開いたらEscapeで戻れる、確認ダイアログが出たらEscapeでキャンセルできる、という動作は多くのソフトウェアで標準的なため、対応していないと操作の流れが途切れます。
実装は単純です。メニューが開いているフラグを持ち、keydownでEscapeを検知したときに閉じる処理を呼ぶだけです。
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && isMenuOpen) closeMenu();
});メニューを閉じたあとは、メニューを開く前にフォーカスがあった要素に戻します。openMenu()を呼ぶ直前にconst trigger = document.activeElementで保存しておき、closeMenu()の末尾でtrigger.focus()を呼ぶのがシンプルな方法です。これを忘れると、閉じたあとにフォーカスが宙に浮き、ユーザーがどこにいるか分からなくなります。
次の行動
まず自分のゲームのメニュー項目を開発者ツールで確認し、Tabキーだけで全項目を巡回できるかテストしてみてください。フォーカスが当たらない要素があれば、tabindex="0"の追加から始めるのが最小の一歩です。矢印キー移動とEscapeキーの対応を追加したら、キーボードだけでタイトルからゲーム開始まで完結するかどうかを通しで確認してみましょう。完成したゲームはアソバースにZIPまたはHTMLファイルとして投稿し、実際のブラウザ環境での動作を確かめてください。
参考資料
- WAI-ARIA Authoring Practices Guide(W3C)
- MDN Web Docs「キーボードナビゲーション」
- MDN Web Docs「:focus-visible」

