Web制作で「ボタンを押したらメニューを開く」「クリックでポップアップを表示する」といった処理を作ろうとaddEventListener('click', ...)を書いたものの、「ボタンを押しても何も反応しない」「コンソールにも何も出ない」というトラブルに直面したことはないでしょうか。

クリックイベントが動かないとき、「addEventListener自体がバグで壊れている」と考える必要はありません。この問題は、以下の3つの段階に分けて確認していくと確実に原因を突き止められます。

  1. 対象要素を取得できているか(前段階のDOM取得)
  2. clickイベントを正しく登録できているか(登録段階)
  3. クリックされた後の処理が動いているか(発火・実行段階)

この記事では、addEventListenerのclickが動かない7つの典型原因と解決策、そしてconsole.logを使った確実なデバッグ手順を解説します。

addEventListenerのclickが動かないときにまず確認すること

コードを複雑に書き直す前に、まずは以下のチェックリストを確認してみてください。

  1. querySelector()で対象要素を取得できているか(中身がnullではないか)
  2. addEventListener('click', ...)のイベント名やコールバック関数の書き方を間違えていないか
  3. イベントを登録する行より手前でJavaScriptエラーが発生していないか
  4. ページ読み込み完了後に動的追加・再生成(置換)された要素ではないか
  5. 同じクラスの要素が複数あるのに、querySelector()で1つにしか登録していないか
  6. ボタンにdisabledが付いていたり、CSSのpointer-events: noneや別要素の重なりで遮られていないか
  7. <form>内のボタンやリンク遷移によって、クリック直後に画面がリロードされていないか

addEventListenerとは?

addEventListener()は、特定の要素に対して「指定したイベント(クリックやスクロールなど)が発生したときに実行する処理」を登録するためのメソッドです。

<button class="js-button">クリック</button>
const button = document.querySelector('.js-button');

// button要素に対して click イベントを登録する
button.addEventListener('click', () => {
  console.log('クリックされました');
});

基本構文は次のようになります。

要素.addEventListener('イベント名', 実行する関数);

対象要素をクリックした瞬間、登録しておいたコールバック関数(() => { ... }の部分)がブラウザによって呼び出されます。

フォームで使うinput・change・blur・submitの違いについては、フォームイベントの使い分けをまとめた記事で詳しく解説しています。

原因1:対象要素を取得できていない

クリックが動かない原因として非常によくあるのが、addEventListenerを呼び出す対象要素そのものが正しく取得できていないケースです。

const button = document.querySelector('.js-button');

console.log(button); // null(要素が見つかっていない)

button.addEventListener('click', () => {
  console.log('クリック');
});
// → TypeError: Cannot read properties of null (reading 'addEventListener')

buttonの中身がnull(該当なし)になっていると、イベントを登録する段階でスクリプトがエラーになり、クリックしても何も動きません。

セレクタの指定ミス(.や#の付け忘れ)や、<script>の実行タイミングによるnullの原因と解決策については、以下の記事で詳しく解説しています。

querySelectorで要素を取得できない・nullになる原因と確認方法

原因2:イベント名やコールバックの書き方を間違えている

文法エラーにはならず静かに失敗する、初心者がつまずきやすいパターンです。

1. イベント名の大文字・小文字ミス

addEventListener()に指定するイベント名は大文字・小文字を区別します。標準のクリックイベントは小文字のclickです。

// ✕ 標準のclickイベントとしては動かない
button.addEventListener('Click', () => {
  console.log('クリック');
});

// ◯ 正しい記述(小文字)
button.addEventListener('click', () => {
  console.log('クリック');
});

2. コールバック関数に()を付けてその場で実行してしまっている

外部で定義した関数をイベントハンドラとして渡す際、関数名の後ろに()を付けてしまうミスです。

function handleClick() {
  console.log('クリック');
}

// ✕ 誤り:登録時にその場で実行されてしまう
button.addEventListener('click', handleClick());

// ◯ 正しい記述1:関数そのもの(参照)を渡す
button.addEventListener('click', handleClick);

// ◯ 正しい記述2:無名関数の中で呼び出す
button.addEventListener('click', () => {
  handleClick();
});

handleClick()と書くと、イベント登録時にその場で関数を実行し、その戻り値をaddEventListener()の第2引数として渡してしまいます。通常の関数で戻り値を指定していなければundefinedになるため、クリック時に呼び出したい関数が登録されません。

クリックされたときにhandleClickを実行したい場合は、()を付けずに関数そのものを渡します。

原因3:addEventListenerより前でJavaScriptがエラーになっている

「クリックしたボタン周辺のコードは完璧なのに動かない」という場合、その行より手前の処理で別のJavaScriptエラーが発生し、スクリプトの実行が途中で停止している可能性があります。

const menu = document.querySelector('.menu');

// ここで未定義関数の呼び出しなど、別のエラーが発生
notDefinedFunction();

// ↑ ここで処理が中断するため、以下のイベント登録まで到達しない
menu.addEventListener('click', () => {
  console.log('クリック');
});

ブラウザの開発者ツール(F12)の「Console」タブを開き、赤いエラーメッセージが上に表示されていないか確認してください。手前のエラーを解消すれば、後続のaddEventListenerも正常に登録されます。

原因4:後から追加・置換された要素にイベントを設定しようとしている

JavaScriptで後からHTML要素を追加(生成)したり、HTML文字列を差し替えたりするような動的UIでよく起こる問題です。

// 1. ページ読み込み時に要素を探す(まだ画面にないため null)
const button = document.querySelector('.dynamic-button');

// この時点では button が null なので、イベントは登録されない
if (button) {
  button.addEventListener('click', () => {
    console.log('クリック');
  });
}

// 2. その後、ボタンがHTMLに挿入される
container.insertAdjacentHTML(
  'beforeend',
  '<button class="dynamic-button">追加</button>'
);

要素がHTML上に作成される前にquerySelectorとaddEventListenerを実行しても、イベントは紐づきません。

対策:要素を追加した直後にイベントを登録する

もっともシンプルな解決策は、要素をDOMに追加した直後に取得してイベントを設定する方法です。

// 要素をDOMへ追加
container.insertAdjacentHTML(
  'beforeend',
  '<button class="dynamic-button">追加</button>'
);

// 追加された直後に取得して登録する
const dynamicButton = container.querySelector('.dynamic-button');
dynamicButton.addEventListener('click', () => {
  console.log('追加されたボタンがクリックされました');
});

また、イベント登録後にcontainer.innerHTML = '...'などで対象要素そのものを作り直した場合、新しく生成された要素には以前のイベントリスナーは引き継がれません。この場合も、新しい要素へイベントを登録し直すか、イベントデリゲーションを利用します。

後から追加した要素へのイベント登録については、イベントデリゲーションを使った方法を別記事で詳しく解説しています。

※要素の追加や削除が頻繁に行われるリストUIなどの場合は、親要素にイベントを1つだけ設定して子要素のクリックを監視する「イベントデリゲーション」という手法を使うと効率的に管理できます。

原因5:複数の要素にイベントを付けたいのにquerySelectorを使っている

同じクラスを持つボタンが複数並んでいる場合、querySelector()では最初の1つにしかイベントが付きません。

<button class="js-btn">ボタン1</button>
<button class="js-btn">ボタン2</button>
<button class="js-btn">ボタン3</button>
// ✕ 最初の「ボタン1」にしか登録されない
const button = document.querySelector('.js-btn');
button.addEventListener('click', () => {
  console.log('クリック');
});

「ボタン2やボタン3を押しても何も起きない」という場合は、querySelectorAll()を使い、すべての要素に対してループ処理でイベントを設定します。

// ◯ 全要素を取得して1つずつ登録する
const buttons = document.querySelectorAll('.js-btn');

buttons.forEach(button => {
  button.addEventListener('click', () => {
    console.log('クリックされました:', button.textContent);
  });
});

原因6:disabled・pointer-events・別要素の重なりでクリックできない

JavaScriptのコードは正常に登録されているにもかかわらず、HTMLやCSSの要因で、クリック操作が対象要素まで届いていないケースです。

1. disabled属性が付いている

<button>など、disabled属性に対応するフォームコントロールにdisabledが付いている場合、ユーザーはそのコントロールを操作できず、通常はクリックイベントも発生しません。

<button class="js-button" disabled>送信</button>

2. CSSのpointer-events: noneが当たっている

対象要素にpointer-events: none;が指定されていると、その要素自身はマウスやタッチなどのポインターイベントの対象になりません。そのため、通常のマウスクリックではその要素に設定したclick処理が反応しなくなります。

.js-button {
  pointer-events: none; /* ポインター操作の対象から外れる */
}

3. 透明な別要素が上に重なっている

モーダルの背景オーバーレイ、透明なヘッダー、あるいはposition: absoluteで配置された要素がボタンの上に覆い被さっていると、クリックはボタンではなく手前の要素に吸い取られます。

DevToolsの「Elements(要素)」パネルでボタンを右クリックして「検証」を選択し、意図したボタン要素が一番手前にあるかを確認してください。

原因7:clickは動いているがフォーム送信やリンク遷移で分かりにくい

「クリックしても動いていないように見える」現象の中で、実はイベント自体は発火しているのに、直後に画面遷移が発生してコンソールが消えているパターンがあります。

<form>
  <!-- type未指定のボタンはデフォルトで type="submit" として扱われる -->
  <button class="js-button">送信</button>
</form>
const button = document.querySelector('.js-button');

button.addEventListener('click', () => {
  console.log('クリックされました'); // 実際には一瞬だけ出力されている
});

<form>内でフォームに関連付けられた<button>は、typeを省略すると通常type="submit"として動作します。そのため、クリック処理が走った直後にブラウザがページをリロード(フォーム送信)してしまい、コンソールのログが即座に消えてしまうのです。

対策:type="button"を明記する

フォーム送信を行わない単なるUIボタンであれば、type="button"を明示して、フォーム送信を行わないボタンとして扱います。

<button type="button" class="js-button">クリック</button>

preventDefault()を付ければclickが動くわけではない

フォームやリンクを扱う際、以下のように書くことがあります。

button.addEventListener('click', (event) => {
  event.preventDefault(); // フォーム送信やリンク遷移などの既定動作をキャンセルする
  console.log('送信を止めてJS処理を実行');
});

ここで知っておくべきなのは、event.preventDefault()は「ブラウザが本来行う既定動作(ページ遷移など)を防ぐ」ための命令であり、動かないクリックイベントを直すためのものではないということです。

「対象要素がない」「文法が間違っている」といった根本原因を解決しないままpreventDefault()を追加しても、イベント自体が動くようにはなりません。

addEventListenerの登録・発火を確認する方法

トラブルが起きたときは、「イベントが動いていない」と全体を眺めるのではなく、処理を「手前・中間・奥」に分解してデバッグします。

const button = document.querySelector('.js-button');

// ステップ1: 要素が取得できているか
console.log('要素の取得結果:', button);

button.addEventListener('click', () => {
  // ステップ2: イベントが発火したか
  console.log('clickイベント発火!');

  // ステップ3: クリック後の独自処理
  openMenu();
});

ボタンを押した瞬間にclickイベント発火!が出る場合:

イベントの登録と発火は完全に成功しています。動かない原因はaddEventListenerではなく、その奥にあるopenMenu()などの処理内容(クラス名の付け替えミスなど)にあります。

ボタンを押しても何も出ない場合:

イベントを登録できていない、またはクリック操作が対象要素まで届いていない可能性があります。ステップ1のログがnullではないか、手前でエラーが出ていないか、disabledやCSSによってクリックが妨げられていないかを確認します。

addEventListenerでclickが動かないときの確認手順

原因を特定するための実践的な確認フローです。上から順に確かめてみてください。

  1. 要素の中身を確認する:querySelectorで取得した変数をconsole.logしてnullではないか確認する
  2. Consoleの既存エラーを確認する:イベント登録の手前で別のJavaScriptエラーが起きていないか確認する
  3. コールバックの先頭にログを置く:関数の先頭にconsole.log('発火')を置き、登録段階と実行段階を切り分ける
  4. 対象要素の数を確認する:ボタンが複数ある場合、querySelectorAllとforEachになっているか確認する
  5. 要素の生成・置換順序を確認する:動的追加やinnerHTMLによる再生成が行われる場合、生成後にイベントを登録しているか確認する
  6. 物理的なクリック阻害を確認する:disabled、pointer-events: none、透明な要素の被さりがないか確認する
  7. ブラウザの既定動作を確認する:<form>や<a>の場合、クリック直後にリロードや遷移が起きていないか確認する

まとめ

addEventListener('click', ...)が動かないときは、コード全体を一気に疑うのではなく、「要素取得」「イベント登録」「クリック後の処理」のどこで止まっているかを切り分けることが解決への最短ルートです。

  • 前段階の確認:対象要素がnullであればaddEventListenerは実行されない
  • 構文の注意:標準のクリックイベント名は小文字の'click'、関数を渡すときは()を付けない
  • UI構造の確認:複数要素ならquerySelectorAll、動的追加やHTML再生成があるなら追加後にイベントを紐づける
  • 既定動作の注意:フォーム内のボタンはtype="button"を明示して意図しないリロードを防ぐ

関数の先頭にconsole.logを1行挟むだけで、「イベント登録・発火の問題」なのか「クリック後の処理の問題」なのかが即座に判別できるようになります。