コンテキストメニューとは何か
コンテキストメニューは、右クリック(または長押し)した場所や対象に応じて“その場に適した操作”を提示するための UI です。ブラウザ標準のメニューを表示するイベントは contextmenu。ここが重要です:独自メニューを出したいなら、contextmenu で preventDefault を行い、座標 e.clientX / e.clientY に合わせて自作のメニューを表示します。常時抑止は不便になりやすいので、必要な領域でのみ実装するのが基本です。
基本の使い方(表示・非表示・位置合わせ)
右クリックで独自メニューを出す
<style>
#menu { position: fixed; background: #fff; border: 1px solid #ccc; box-shadow: 0 4px 12px rgba(0,0,0,.15); display: none; }
#menu .item { padding: 8px 12px; cursor: pointer; }
#menu .item:hover { background: #f0f4ff; }
</style>
<div id="area" style="height:120px;border:1px solid #ccc">右クリックしてみてください</div>
<div id="menu">
<div class="item" data-action="copy">コピー</div>
<div class="item" data-action="delete">削除</div>
</div>
<script>
const area = document.getElementById("area");
const menu = document.getElementById("menu");
area.addEventListener("contextmenu", (e) => {
e.preventDefault(); // 標準メニューを止める
// 表示位置(ビューポート座標)
const x = e.clientX, y = e.clientY;
menu.style.left = x + "px";
menu.style.top = y + "px";
menu.style.display = "block";
});
// 外側クリックで閉じる
document.addEventListener("click", () => menu.style.display = "none");
</script>
HTMLここが重要です:表示は“クリック位置に固定配置”が簡単で直感的。閉じ方は“外側クリックで閉じる”をセットで実装して、出しっぱなしを防ぎます。
メニューの項目選択(イベント委譲でシンプルに)
どの項目が選ばれたかを判定して処理する
<script>
menu.addEventListener("click", (e) => {
const item = e.target.closest(".item");
if (!item) return;
const action = item.dataset.action;
menu.style.display = "none"; // 先に閉じる
if (action === "copy") console.log("コピー");
if (action === "delete") console.log("削除");
});
</script>
HTMLここが重要です:項目が増えても“親で一括”のイベント委譲ならコードがシンプル。クリック直後に閉じることで誤操作を避け、フィードバックを素早く返します。
はみ出し対策とスクロール連動(画面端でもきれいに)
画面端では位置を補正する
<script>
function placeMenu(x, y) {
const rect = menu.getBoundingClientRect();
const vw = window.innerWidth, vh = window.innerHeight;
const nx = Math.min(x, vw - rect.width - 8);
const ny = Math.min(y, vh - rect.height - 8);
menu.style.left = nx + "px";
menu.style.top = ny + "px";
}
area.addEventListener("contextmenu", (e) => {
e.preventDefault();
menu.style.display = "block";
placeMenu(e.clientX, e.clientY);
});
</script>
HTMLここが重要です:右下隅でメニューが切れないよう、幅・高さを使って“最大位置”を計算してから置くのが定石です。小さな余白を残すと見栄えが安定します。
スクロール時の扱い
メニューを fixed で置けば、スクロールしても“画面に追従”します。対象要素に紐づけた位置にしたい(要素移動で位置も動かしたい)なら、要素の getBoundingClientRect から座標を取り直して表示し直します。ここが重要です:メニューの意味が“画面位置”か“要素位置”かを決め、スクロールやレイアウト変化への対応方針を統一します。
キーボード・アクセシビリティ(右クリックに頼らない操作経路)
キーボードでも開けるようにする
<button id="openMenu">メニュー</button>
<script>
openMenu.addEventListener("click", (e) => {
// ボタンの直下にメニューを出す例
const r = openMenu.getBoundingClientRect();
menu.style.display = "block";
placeMenu(r.left, r.bottom);
});
// Esc で閉じる
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") menu.style.display = "none";
});
</script>
HTMLここが重要です:右クリックだけに依存するとマウス必須になります。ボタン・キーボード・スクリーンリーダーでも使える導線を用意すると実用度が上がります。role=”menu” や aria 属性で意味付けするとさらに良いです。
サブメニュー・状態管理(ホバーではなく明確な開閉に)
サブメニューの基本(明示的に開閉)
<style>
.submenu { position: absolute; left: 100%; top: 0; display: none; background:#fff; border:1px solid #ccc; }
.item.has-sub:hover .submenu { display: block; } /* 簡易版。必要ならクリックで開閉にする */
</style>
<div id="menu">
<div class="item has-sub">もっと
<div class="submenu">
<div class="item" data-action="opt1">オプション1</div>
<div class="item" data-action="opt2">オプション2</div>
</div>
</div>
</div>
HTMLここが重要です:ホバーだけに頼ると誤開閉が増えます。クリックで“明確に開く/閉じる”ほうが安定します。開いたら他は閉じるなど、状態は単一ロジックで同期すると混乱が減ります。
モバイル配慮(長押し/代替 UI)
スマホには右クリックがありません。長押しが contextmenu を送る機種もありますが一貫しません。ここが重要です:モバイルでは“アクションメニュー”をボタンで表示する設計に寄せ、長押しに依存しない導線を用意します。アイテム右端に“…”ボタンを置き、同じメニューを開くのが現実的です。
よくある落とし穴と回避策
デフォルトメニューの全域抑止は不便になりがちです。必要な領域だけで preventDefault を使い、外側では標準メニューを生かします。閉じ忘れは“外クリック”と Esc 対応を標準装備にする。画面端のはみ出しは幅・高さで補正し、レイアウト変化時に座標を再計算します。ホバー展開は誤操作を招きやすいので、クリックで明確に開閉する方針が安定。アクセシビリティを意識してボタンやキーボードからも開ける導線を用意すると、誰でも扱える UI になります。
まとめ
コンテキストメニューは“その場の操作”を凝縮する強力な UI です。contextmenu イベントで preventDefault し、e.clientX / e.clientY に合わせて表示、外クリック・Esc で閉じるのが基本形。イベント委譲で選択処理を簡潔にし、画面端では位置補正、スクロールやレイアウト変化には座標の取り直しで対応します。右クリック依存を避け、ボタン・キーボードでも開ける導線を持たせれば、初心者でも実用的で壊れにくいコンテキストメニューを設計できます。
