JavaScript
A safe, universal way to react when something is “pressed”—mouse, tap, or keyboard—so you can switch views, open menus, or stop a submit right after the user’s action.
click
ボタンやリンクを「押した合図」を受け取り、画面の切り替え・送信の中止・メニューの開閉など“押した後に起きてほしいこと”を、マウス・タップ・キーボードの操作を含めて安全に実装できる仕組み。
この記事でわかること
addEventListener()でclickイベントを登録する方法- リンク遷移やフォーム送信など既定動作との関係
- キーボード操作とアクセシビリティを保つ要素の選び方
click とは?
まずイメージから。
「マウスでポチッ」「スマホでトン」「Enterキーで決定」──これらが“クリック”として扱われ、ボタンやリンクを“動かす合図”になります。JavaScriptでは、この合図(イベント)を受け取って、画面を書き換えたり、メニューを開いたり、送信を止めたりできます。
最短メモ:まずはこれだけ
- クリックを受け取る:
element.addEventListener('click', handler) - HTMLに直書きは避ける:
onclick="..."はメンテしづらい - スマホでも
clickは来る(指のタップでOK) - キーボード操作(主にEnterやSpace)も“クリック相当”になる要素がある(
<button>や<a>など) - 動きを止めたいときは
event.preventDefault()、伝播を止めたいときはevent.stopPropagation()
最小サンプル
HTML
<button id="buy">Buy</button>
<script>
const btn = document.getElementById('buy');
btn.addEventListener('click', (e) => {
console.log('買うボタンが押されました!', e);
// ここで購入処理など
});
</script>
どう書くのが“正しい”?:onclick と addEventListener
onclick="..."(HTML属性)- HTMLとJSが混ざり、複数の処理を重ねにくい。学習の最初は便利でも、規模が大きくなると管理が難しい。
element.onclick = handler(プロパティ)- 1つしか設定できない。後から上書きされやすい。
element.addEventListener('click', handler, options)(推奨)- 複数のハンドラを独立して付け外しでき、キャプチャ/バブリングの制御や
once/signalなどモダンな機能が使える。
JavaScript
const handler = (e) => console.log('clicked!');
document.querySelector('#btn').addEventListener('click', handler, { once: true });
クリックが起きるタイミング:やさしい→正確
- やさしい説明
- 「人が押したら起きる」。マウス、タップ、Enterキーなど、人の操作で“押された”と判断できると発火します。
- 正確な説明
clickは「要素のアクティベーション(活性化)」に紐づくイベントです。要素の種類ごとに既定動作(リンクの遷移、ボタンの送信、フォーカス移動など)があり、ユーザー操作やスクリプトからのclick()呼び出しで発火します。キーボードが“クリック相当”になるかは要素次第(例:<button>はEnter/Space、<a>はEnterが相当)です。
キーボードとアクセシビリティ(超大事)
- クリックできる見た目でも、
<div>にclickだけ付けるのはNG。
そのままだとキーボード操作やスクリーンリーダーで使いにくい(フォーカスできない、Spaceキーで反応しない等)。 - まずは意味の合う要素を使う
- 何かを実行する →
<button> - 別ページ/同一ページへ移動 →
<a href="...">
- 何かを実行する →
- どうしても
<div>にしたいときの最低限(推奨はしない)tabindex="0"を付けてフォーカス可能にrole="button"で意味を伝えるkeydownでEnterとSpaceを拾い、click()を呼ぶ- ARIAの使い方は慎重に。誤用は逆効果。
HTML
<div id="like" role="button" tabindex="0" aria-pressed="false">Like</div>
<script>
const el = document.getElementById('like');
const toggle = () => {
const pressed = el.getAttribute('aria-pressed') === 'true';
el.setAttribute('aria-pressed', String(!pressed));
};
el.addEventListener('click', toggle);
el.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault(); // Spaceでページがスクロールするのを防ぐ
toggle();
}
});
</script>
伝播モデル:キャプチャ→ターゲット→バブリング
- クリックはツリーを流れる
- キャプチャ(親 → 子へ降りる)
- ターゲット(実際にクリックされた要素)
- バブリング(子 → 親へ上がる)
- 止め方
event.stopPropagation()… 以降の親へ伝わらないevent.stopImmediatePropagation()… 同じ要素に付いた他のハンドラも止める
- どこで受ける?
“たくさんの子要素”をまとめて扱うなら、親で受ける「イベントデリゲーション」が有効。
HTML
<ul id="list">
<li data-id="1">A</li>
<li data-id="2">B</li>
<li data-id="3">C</li>
</ul>
<script>
const list = document.getElementById('list');
list.addEventListener('click', (e) => {
const li = e.target.closest('li');
if (!li || !list.contains(li)) return;
console.log('クリックされたID:', li.dataset.id);
});
</script>
デフォルト動作と preventDefault の勘所
preventDefault()は既定の“後始末”や“遷移”を止める合図。
例:<a>のページ遷移、<form>の送信、<label>のフォーカス移動など。- SPAでリンクの遷移をJSに任せたいとき
HTML
<a id="toAbout" href="/about">About</a>
<script>
document.getElementById('toAbout').addEventListener('click', (e) => {
e.preventDefault();
// pushStateしてAJAXロードなど
history.pushState({}, '', '/about');
// コンテンツ差し替え処理
});
</script>
注意:止める必要のない場所でpreventDefault()を多用しない。操作性や支援技術に悪影響が出る。
要素ごとの“クリックらしさ”の違い
<button>- Enter/Spaceでアクティベート。
disabledだとclickは発火しない。type="submit"はフォーム送信が既定動作。 <a href>- クリックで遷移。キーボードは主にEnter。
hrefがないとリンクとしての意味が薄れ、支援技術での扱いも変わる。 <label for="id">- クリックで対応するフォーム部品にフォーカスやトグルが入る。
label内のクリックは、実際の入力要素にも影響する(処理の重複に注意)。 disabledなフォーム部品- クリックイベント自体が発火しない(親にバブリングもしない挙動に注意)。“無効”は“触れない”が基本。
contenteditableや選択可能なテキスト- クリックでフォーカス・カーソル移動などが既定動作。選択の邪魔になる
preventDefault()は避けたい。
似て非なるイベントたち
dblclick- ダブルクリック。UX的にはスマホで曖昧なので乱用非推奨。
contextmenu- 右クリック(長押し)メニュー。無効化は慎重に。
auxclick- 中ボタンなどの“補助クリック”。新しいタブで開く動作などと関係。
pointerup/pointerdown/mouseup/mousedown- より低レベル。押された瞬間や離した瞬間が欲しい時に。
element.click() と dispatchEvent(new MouseEvent('click')) の違い
element.click()
多くの要素で“ユーザーが押したときと同等のアクティベーション”を行う高レベルAPI。既定動作も走りやすい(リンク遷移やフォーム送信など)。dispatchEvent(new MouseEvent('click'))
任意のイベントオブジェクトを投げる低レベルAPI。既定動作がフルに再現されないことがある。- セキュリティ観点:ポップアップや自動再生など“ユーザーのジェスチャ必須”機能は、スクリプト合成だと制限されることが多い。
event.isTrustedで“本当にユーザー操作か”判定可能(click()やdispatchEventで発火したイベントは通常false)。
オプション活用:once / capture / passive / signal
JavaScript
const controller = new AbortController();
button.addEventListener('click', onClick, {
capture: false, // 先に親→子で拾うならtrue
once: true, // 一度だけ実行して自動解除
passive: false, // clickでは通常メリット薄い(touch/wheel向け)
signal: controller.signal // 中断で一括解除
});
// 必要になったら controller.abort();
onceは“おみくじボタン”のように“一回きり”に最適。signalは複数リスナをまとめて外すときに強力(クリーンアップが簡単)。
イベント情報を読み解く:よく使うプロパティ
event.target/event.currentTarget- 実際に押された要素 / リスナを付けた要素。デリゲーションでは
closest()と組み合わせるのが定番。 event.detail- クリック回数(1、2…)。ダブルクリック判定に使えるが、スマホ対応は慎重に。
event.button/event.buttons- 押されたボタン(左/中/右)や同時押しの状態。
event.composedPath()- 伝播に使われた要素の配列(Shadow DOMをまたぐときに便利)。
event.isTrusted- ユーザー操作由来なら
true。自動実行やテスト合成ではfalse。
ありがちトラブルと回避策
- 「クリックが2回分走る」
-
labelとinputの両方にリスナを置いて重複実行- 子と親の両方で同じ処理をしていてバブリングで二重化
→stopPropagation()や処理の場所整理、closest()でターゲットを限定
- 「スマホだと反応が遅い」
- 昔は“300ms遅延”があったが、今はViewport設定とダブルタップズーム次第。
<meta name="viewport" content="width=device-width, initial-scale=1">を基本に。 - 「
disabledにクリック付けたのに動かない」 - 仕様通り。
disabled要素はクリック不可。ラッパーや別UIで扱う。 - 「外側クリックでモーダル閉じたい」
- 中身のクリックで閉じないよう、ターゲットの判定を丁寧に。
JavaScript
const modal = document.getElementById('modal');
const overlay = document.getElementById('overlay');
overlay.addEventListener('click', () => closeModal());
// モーダル内をクリックしても閉じない
modal.addEventListener('click', (e) => e.stopPropagation());
レシピ集(実務で即使える最小コード)
メニューのトグル(外側クリックで閉じる付き)
HTML
<button id="menuBtn" aria-expanded="false" aria-controls="menu">Menu</button>
<nav id="menu" hidden>...</nav>
<script>
const btn = document.getElementById('menuBtn');
const menu = document.getElementById('menu');
const open = () => {
btn.setAttribute('aria-expanded', 'true');
menu.hidden = false;
document.addEventListener('click', onOutside, { signal: outsideController.signal });
};
const outsideController = new AbortController();
const onOutside = (e) => {
if (e.target.closest('#menu') || e.target.closest('#menuBtn')) return;
close();
};
const close = () => {
btn.setAttribute('aria-expanded', 'false');
menu.hidden = true;
outsideController.abort(); // outsideリスナを一括解除
};
btn.addEventListener('click', () => {
const expanded = btn.getAttribute('aria-expanded') === 'true';
expanded ? close() : open();
});
</script>
SPAリンク(既定の遷移を止めてJSで切替)
HTML
<a href="/settings" id="toSettings">Settings</a>
<main id="app"></main>
<script>
document.getElementById('toSettings').addEventListener('click', (e) => {
e.preventDefault();
history.pushState({}, '', '/settings');
document.getElementById('app').textContent = 'Settings Page';
});
</script>
一回限りの購入処理
JavaScript
document.getElementById('buy').addEventListener('click', async (e) => {
e.currentTarget.disabled = true;
try {
await purchase();
} finally {
e.currentTarget.disabled = false;
}
}, { once: true });
安全なイベントデリゲーション(複数ボタン対応)
HTML
<ul id="actions">
<li><button data-action="edit">Edit</button></li>
<li><button data-action="delete">Delete</button></li>
</ul>
<script>
document.getElementById('actions').addEventListener('click', (e) => {
const btn = e.target.closest('button[data-action]');
if (!btn) return;
const action = btn.dataset.action;
if (action === 'edit') editItem();
if (action === 'delete') deleteItem();
});
</script>
“本当に人が押したときだけ”許可したい
JavaScript
document.getElementById('play').addEventListener('click', (e) => {
if (!e.isTrusted) return; // 自動実行やbotを弾く
video.play();
});
設計のコツ(パフォーマンス・保守性)
- クリックは“入り口”。ロジックは関数に分離してテストしやすく。
- 子要素が大量なら、親で一括管理(デリゲーション)でリスナ数を減らす。
- 解除タイミングを設計する(
once、AbortController、画面遷移時のクリーンアップ)。 - UIの意味に合うタグ選び(まず
<button>と<a>)。アクセシビリティ最優先。 - “止める系API”は最小限(
preventDefault/stopPropagationの乱用は保守性低下)。
まとめの要点
addEventListener('click', ...)が基本。- デリゲーションと
closest()で賢く拾う。 - 既定動作は味方。必要な場面だけ
preventDefault()。 - アクセシビリティは“正しい要素選び+必要十分な補強”。
once/signalでライフサイクル管理。element.click()とdispatchEvent(...)は目的で使い分け。