マウスイベントの基礎(click / dblclick / mouseover)
マウスイベントは「ユーザーの操作」をコードへ橋渡しする合図です。click は単押し、dblclick は素早い二連続クリック、mouseover はカーソルが要素に入った瞬間のイベント。ここが重要です:目的に合わせてイベントを選び、既定動作や伝播、座標などの文脈を正しく扱うと、意図通りで滑らかな UI を作れます。
click(単クリック)を使いこなす
基本の登録と既定動作の制御
<a id="go" href="/next">次へ</a>
<script>
const go = document.getElementById("go");
go.addEventListener("click", (e) => {
e.preventDefault(); // 既定のページ遷移を止める
go.textContent = "移動中…";
history.pushState({}, "", "/next"); // SPA 的な遷移
});
</script>
HTMLここが重要です:リンクやフォームは「既定動作」があります。アプリ内で制御したいときだけ preventDefault を使い、乱用しない。
ショートクリックの UI 更新
<button id="buy">購入</button>
<script>
const buy = document.getElementById("buy");
buy.addEventListener("click", async () => {
buy.disabled = true;
buy.textContent = "購入中…";
try { await new Promise(r => setTimeout(r, 600)); }
finally { buy.disabled = false; buy.textContent = "購入"; }
});
</script>
HTMLここが重要です:視覚(文言)と操作不可(disabled)を同じロジックで同期させると、状態のズレが起きません。
dblclick(ダブルクリック)の扱い方
基本の登録と click との競合
<div id="box" style="width:120px;height:80px;border:1px solid #ccc"></div>
<script>
const box = document.getElementById("box");
box.addEventListener("click", () => console.log("単クリック"));
box.addEventListener("dblclick", () => console.log("ダブルクリック"));
</script>
HTMLここが重要です:多くのブラウザでは、dblclick が発火する前に click が2回発生します。ダブルクリック専用動作をしたい場合は、単クリックの処理を“遅延”して、ダブルクリックが来たらキャンセルする戦略が有効です。
遅延キャンセルの例(単クリックとダブルクリックを両立)
<div id="item">項目</div>
<script>
const item = document.getElementById("item");
let clickTimer = null;
item.addEventListener("click", () => {
clearTimeout(clickTimer);
clickTimer = setTimeout(() => {
console.log("単クリックの動作");
}, 250); // ダブルクリック判定待ち
});
item.addEventListener("dblclick", () => {
clearTimeout(clickTimer); // 単クリックをキャンセル
console.log("ダブルクリックの動作");
});
</script>
HTMLここが重要です:単クリックは少し遅らせ、ダブルクリックが来たら単クリックを取り消す。ユーザー体感を損ねない最小遅延に調整します。
mouseover(カーソルが入った瞬間)と hover の作法
mouseover と mouseenter の違い
<div id="wrap" style="padding:16px;border:1px solid #ccc">
<button id="btn">ボタン</button>
</div>
<script>
wrap.addEventListener("mouseover", () => console.log("wrap上にマウスが入るたび(子をまたいでも発火)"));
wrap.addEventListener("mouseenter", () => console.log("wrapに初めて入ったときだけ"));
</script>
HTMLここが重要です:mouseover は子要素間の移動でも繰り返し発火します。初回だけに反応したいなら mouseenter を使います(バブリングしない点に注意)。
ふわっと表示(hover intent を意識)
<div id="tip" style="display:none">ヒント</div>
<button id="help">ヘルプ</button>
<script>
let hoverTimer;
help.addEventListener("mouseover", () => {
clearTimeout(hoverTimer);
hoverTimer = setTimeout(() => tip.style.display = "block", 120);
});
help.addEventListener("mouseout", () => {
clearTimeout(hoverTimer);
tip.style.display = "none";
});
</script>
HTMLここが重要です:微小な通過で誤発火しないよう、短い遅延を入れて“意図的な滞在”だけに反応させると UX が安定します。
座標・ボタン・修飾キー(詳細情報の活用)
クリック位置とボタン種類
<div id="pad" style="height:120px;border:1px solid #ccc"></div>
<script>
pad.addEventListener("click", (e) => {
const rect = pad.getBoundingClientRect();
const x = e.clientX - rect.left; // 要素内X
const y = e.clientY - rect.top; // 要素内Y
console.log("位置:", x, y, "ボタン:", e.button); // 0:左, 1:中, 2:右
});
</script>
HTMLここが重要です:描画やドラッグでは“要素内座標”に正規化すると扱いやすく、スクロールの影響も抑えられます。
修飾キーでショートカット
<button id="save">保存</button>
<script>
save.addEventListener("click", (e) => {
if (e.shiftKey) console.log("別名で保存");
else console.log("通常保存");
});
</script>
HTMLここが重要です:shiftKey/ctrlKey/metaKey/altKey を組み合わせて、クリックにもショートカット的な分岐を与えられます。
伝播・委譲・アクセシビリティ(設計の要点)
イベント委譲で規模に強くする
<ul id="list"></ul>
<script>
list.addEventListener("click", (e) => {
const del = e.target.closest(".delete");
if (!del || !list.contains(del)) return;
del.closest("li")?.remove();
});
const li = document.createElement("li");
li.innerHTML = `りんご <button class="delete">削除</button>`;
list.appendChild(li);
</script>
HTMLここが重要です:親1箇所で子の操作に反応する委譲は、動的追加に強く、リスナー数を抑えられます。判定は target+closest、範囲確認は currentTarget.contains。
stopPropagation は慎重に
- 指針: 必要な場面だけ使い、基本は条件分岐で絞り込む。
- 理由: 親で委譲している処理を“遮断”してしまい、デバッグが難しくなる。
キーボード・タッチを忘れない
- 補完: ボタンには Enter/Space のキーボード操作を想定し、可能ならネイティブ button を使う。
- タッチ: スマホでは click の発火タイミングや dblclick の体験が異なる。タップ操作を別途設計する。
よくある落とし穴と回避策
- 単/ダブルクリックの競合:
単クリックを遅延し、ダブルクリックが来たらキャンセルする。 - mouseover の過剰発火:
子間移動で連発する。初回だけなら mouseenter、ふわっと表示は短い遅延で“意図”を判定。 - 座標の誤用:
要素内座標に正規化して使う。page/client の違いを理解する。 - preventDefault の乱用:
既定動作を置き換える明確な意図があるときだけ使う。無闇に止めない。 - 委譲と stopPropagation の衝突:
委譲前提の設計では stopPropagation を控え、closest と条件分岐で丁寧に処理。
実践例(ギャラリー、テーブル編集、メニューのホバー)
画像ギャラリー:単クリックで選択、ダブルクリックで拡大
<ul id="gallery"></ul>
<script>
const g = document.getElementById("gallery");
let timer;
g.addEventListener("click", (e) => {
const item = e.target.closest("li");
if (!item) return;
clearTimeout(timer);
timer = setTimeout(() => item.classList.toggle("selected"), 200);
});
g.addEventListener("dblclick", (e) => {
const item = e.target.closest("li");
if (!item) return;
clearTimeout(timer);
// 拡大表示を開く
console.log("拡大:", item);
});
</script>
HTMLテーブル行:マウスオーバーで編集アイコン表示
<table id="grid">
<tr><td>りんご</td><td><span class="edit" style="display:none">✎</span></td></tr>
</table>
<script>
grid.addEventListener("mouseenter", (e) => {
const row = e.target.closest("tr");
row?.querySelector(".edit").style.display = "inline";
}, { capture: false });
grid.addEventListener("mouseleave", (e) => {
const row = e.target.closest("tr");
row?.querySelector(".edit").style.display = "none";
});
</script>
HTMLメニュー:ホバーで展開、外に出たら閉じる
<nav id="menu">
<div class="root">製品</div>
<div class="panel" hidden>一覧…</div>
</nav>
<script>
const menu = document.getElementById("menu");
const panel = menu.querySelector(".panel");
let hoverTimer;
menu.addEventListener("mouseover", (e) => {
if (!e.target.closest(".root")) return;
clearTimeout(hoverTimer);
hoverTimer = setTimeout(() => panel.hidden = false, 120);
});
menu.addEventListener("mouseout", (e) => {
if (panel.hidden) return;
if (!menu.contains(e.relatedTarget)) { // 完全に外へ出た
clearTimeout(hoverTimer);
panel.hidden = true;
}
});
</script>
HTMLまとめ
click は単押し、dblclick は二連続、mouseover はカーソル侵入。単/ダブルクリックの競合は“遅延キャンセル”で両立し、hover は mouseenter や短い遅延で過剰発火を抑える。座標は要素内へ正規化し、委譲+closest/contains で規模に強くする。preventDefault/stopPropagation は必要最小限に。これらを徹底すれば、初心者でも意図通りで滑らかなマウスインタラクションが確実に書けます。
