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

ボタンやリンクを「押した合図」を受け取り、画面の切り替え・送信の中止・メニューの開閉など“押した後に起きてほしいこと”を、マウス・タップ・キーボードの操作を含めて安全に実装できる仕組み。

click とは?

まずイメージから。

「マウスでポチッ」「スマホでトン」「Enterキーで決定」──これらが“クリック”として扱われ、ボタンやリンクを“動かす合図”になります。JavaScriptでは、この合図(イベント)を受け取って、画面を書き換えたり、メニューを開いたり、送信を止めたりできます。

最短メモ:まずはこれだけ

最小サンプル

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が相当)です。

キーボードとアクセシビリティ(超大事)

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>

伝播モデル:キャプチャ→ターゲット→バブリング

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 の勘所

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>
クリックで遷移。キーボードは主にEnterhrefがないとリンクとしての意味が薄れ、支援技術での扱いも変わる。
<label for="id">
クリックで対応するフォーム部品にフォーカスやトグルが入る。label内のクリックは、実際の入力要素にも影響する(処理の重複に注意)。
disabledなフォーム部品
クリックイベント自体が発火しない(親にバブリングもしない挙動に注意)。“無効”は“触れない”が基本。
contenteditableや選択可能なテキスト
クリックでフォーカス・カーソル移動などが既定動作。選択の邪魔になるpreventDefault()は避けたい。

似て非なるイベントたち

dblclick
ダブルクリック。UX的にはスマホで曖昧なので乱用非推奨。
contextmenu
右クリック(長押し)メニュー。無効化は慎重に。
auxclick
中ボタンなどの“補助クリック”。新しいタブで開く動作などと関係。
pointerup/pointerdown / mouseup/mousedown
より低レベル。押された瞬間や離した瞬間が欲しい時に。

element.click() と dispatchEvent(new MouseEvent('click')) の違い

オプション活用: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();

イベント情報を読み解く:よく使うプロパティ

event.target / event.currentTarget
実際に押された要素 / リスナを付けた要素。デリゲーションではclosest()と組み合わせるのが定番。
event.detail
クリック回数(1、2…)。ダブルクリック判定に使えるが、スマホ対応は慎重に。
event.button / event.buttons
押されたボタン(左/中/右)や同時押しの状態。
event.composedPath()
伝播に使われた要素の配列(Shadow DOMをまたぐときに便利)。
event.isTrusted
ユーザー操作由来ならtrue。自動実行やテスト合成ではfalse

ありがちトラブルと回避策

「クリックが2回分走る」
  • labelinputの両方にリスナを置いて重複実行
  • 子と親の両方で同じ処理をしていてバブリングで二重化
    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();
});

設計のコツ(パフォーマンス・保守性)

まとめの要点