JavaScriptボタンで画面が動かない?私がずっと悩んだクリックイベントの罠

javascript icon
JavaScript

「ボタン作ったのに、押しても何も起きない...」

こんにちは、とまだです。

初めてJavaScriptでボタンを作った時、私はこの問題でかなり悩みました。

HTMLにボタンは表示されてる。でもクリックしても無反応。

「なんで?コード間違ってないはずなのに...」

そんな経験、ありませんか?

実は、ボタンってWebサイトの顔なんです。ユーザーが最初に触る部分だから。

でも意外と奥が深くて、初心者が陥りやすい罠がたくさんあるんです。

今回は現役のエンジニア、そして元プログラミングスクール講師としての経験から、JavaScriptのボタン実装について解説します。

なぜボタンでみんなつまずくのか(私の失敗談)

私が最初にボタンで失敗した理由。それは「HTMLとJavaScriptの関係」を理解してなかったからです。

<!-- これ、動かなかった私の最初のコード -->
<button>クリックしてね</button>

<script>
  // ボタンを押したら「Hello!」って出したかった
  alert("Hello!");
</script>

「え?何がダメなの?」って思いますよね。

これ、ボタンとJavaScriptが全く繋がってないんです。

HTMLのボタンと、JavaScriptの処理をつなぐ「接着剤」が必要だったんです。

ボタンの基本構造(実は2種類ある)

簡単に言うと、HTMLでボタンを作る方法は主に2つあります。

1. button要素(これを使えばOK)

<button>クリックしてください</button>

シンプルで分かりやすい。私はこれしか使いません。

2. input要素(昔の名残)

<input type="button" value="クリックしてください">

古いプロジェクトで見かけることがあります。でも今はbutton要素の方が主流です。

なぜbutton要素がいいの?

<!-- button要素ならアイコンも入れられる! -->
<button>
  <i class="icon-cart"></i> カートに入れる
</button>

<!-- input要素だとテキストしか入れられない... -->
<input type="button" value="カートに入れる">

ボタンの中にアイコンを入れたい時、button要素なら簡単にできるんです。

魔法の呪文:addEventListener

ボタンとJavaScriptをつなぐ魔法の呪文があります。

それが addEventListener です。

// 1. まずボタンを取得
const myButton = document.getElementById("myButton");

// 2. ボタンにイベントリスナーを付ける
myButton.addEventListener("click", () => {
  alert("やった!ボタンが動いた!");
});

「addEventListener とは?」

簡単に言うと、「このボタンがクリックされたら、こうしてね」という指示を与える命令です。

私はこれを「ボタンに耳を付ける」って覚えました。

ボタンが「クリックされた!」って聞いたら、指定した処理を実行するんです。

実際に使える!便利なボタン実装3選

実務でよく使うボタンの実装を3つ紹介します。

1. 送信ボタンの二重送信防止

「送信ボタン連打されて、同じデータが何個も送られちゃった...」

これ、実際に起きた私の失敗です。

<button id="submitBtn">送信する</button>

<script>
const submitBtn = document.getElementById("submitBtn");

submitBtn.addEventListener("click", function() {
  // ボタンを無効化(これが重要!)
  this.disabled = true;
  this.textContent = "送信中...";

  // ここで送信処理
  // 例:APIを呼ぶなど

  // 処理が終わったら再度有効化(必要に応じて)
  setTimeout(() => {
    this.disabled = false;
    this.textContent = "送信する";
  }, 3000);
});
</script>

ボタンを押したら即座に無効化。これで連打を防げます。

2. モーダル(ポップアップ)の表示

「詳細を見る」ボタンを押したら、画面の上に情報が出てくるやつ。

あれ、実は簡単に作れます。

<!-- ボタン -->
<button id="openModal">詳細を見る</button>

<!-- モーダル(最初は非表示) -->
<div id="myModal" class="modal" style="display: none;">
  <div class="modal-content">
    <h3>商品の詳細情報</h3>
    <p>ここに詳細な説明が入ります。</p>
    <button id="closeModal">閉じる</button>
  </div>
</div>

<script>
const openBtn = document.getElementById("openModal");
const closeBtn = document.getElementById("closeModal");
const modal = document.getElementById("myModal");

// 開くボタン
openBtn.addEventListener("click", () => {
  modal.style.display = "block";
});

// 閉じるボタン
closeBtn.addEventListener("click", () => {
  modal.style.display = "none";
});
</script>

3. タブ切り替え(めっちゃ使う)

商品説明、レビュー、Q&Aをタブで切り替えるやつです。

<!-- タブボタン -->
<div class="tab-buttons">
  <button class="tab-btn active" data-tab="desc">商品説明</button>
  <button class="tab-btn" data-tab="review">レビュー</button>
  <button class="tab-btn" data-tab="qa">Q&A</button>
</div>

<!-- タブコンテンツ -->
<div id="desc" class="tab-content active">商品の説明...</div>
<div id="review" class="tab-content" style="display: none;">レビュー...</div>
<div id="qa" class="tab-content" style="display: none;">Q&A...</div>

<script>
const tabButtons = document.querySelectorAll(".tab-btn");
const tabContents = document.querySelectorAll(".tab-content");

tabButtons.forEach(button => {
  button.addEventListener("click", () => {
    // 全部非表示にして
    tabContents.forEach(content => {
      content.style.display = "none";
    });

    // クリックされたタブだけ表示
    const tabId = button.getAttribute("data-tab");
    document.getElementById(tabId).style.display = "block";

    // ボタンのアクティブ状態も更新
    tabButtons.forEach(btn => btn.classList.remove("active"));
    button.classList.add("active");
  });
});
</script>

ボタンのデザイン(見た目も大事)

機能は動いた。でも見た目がダサい...

そんな時はCSSでおしゃれにしましょう。

/* 基本のボタンスタイル */
.btn {
  /* 見た目の基本設定 */
  background-color: #3498db;  /* 青色 */
  color: white;               /* 文字は白 */
  padding: 12px 24px;         /* 内側の余白 */
  border: none;               /* 枠線なし */
  border-radius: 6px;         /* 角を丸く */
  font-size: 16px;
  cursor: pointer;            /* カーソルを指マークに */

  /* アニメーション */
  transition: all 0.3s ease;
}

/* マウスを乗せた時 */
.btn:hover {
  background-color: #2980b9;  /* 少し濃い青 */
  transform: translateY(-2px);  /* ちょっと浮く */
  box-shadow: 0 4px 8px rgba(0,0,0,0.1);  /* 影をつける */
}

/* クリックした時 */
.btn:active {
  transform: translateY(0);   /* 押し込まれる感じ */
  box-shadow: none;
}

ポイント:

  • cursor: pointer で指マークになる(これ忘れがち)
  • transition でなめらかに動く
  • ホバーで少し浮かせると「押せる感」が出る

よくある失敗と対処法

1. ボタンが取得できない

// ❌ よくある失敗
const btn = document.getElementById("myButton");
btn.addEventListener("click", () => {  // エラー!
  console.log("クリック!");
});

「Cannot read property 'addEventListener' of null」

このエラー、めちゃくちゃ見ます。

原因: HTMLが読み込まれる前にJavaScriptが実行されてる

解決策:

// ✅ 解決方法1:HTMLの最後に書く
<!-- すべてのHTML要素 -->
<script>
  // ここならHTMLは読み込み済み
</script>

// ✅ 解決方法2:DOMContentLoadedを使う
document.addEventListener("DOMContentLoaded", () => {
  const btn = document.getElementById("myButton");
  btn.addEventListener("click", () => {
    console.log("クリック!");
  });
});

2. thisの罠

// ❌ アロー関数でthisを使うと...
button.addEventListener("click", () => {
  console.log(this);  // Window オブジェクト(想定外!)
});

// ✅ 通常の関数ならthisはボタン要素
button.addEventListener("click", function() {
  console.log(this);  // クリックされたボタン要素
  this.style.backgroundColor = "red";
});

3. イベントの重複登録

// ❌ ループ内で登録すると重複する可能性
for (let i = 0; i < 3; i++) {
  button.addEventListener("click", () => {
    console.log("クリック");
  });
}
// 1回クリックで3回実行される!

プロが教える実装のコツ

1. ボタンには必ずtype属性を

<!-- ❌ type属性なしだと... -->
<form>
  <button>キャンセル</button>  <!-- フォームが送信される! -->
</form>

<!-- ✅ type属性で挙動を明確に -->
<form>
  <button type="button">キャンセル</button>  <!-- 送信されない -->
  <button type="submit">送信</button>       <!-- 送信される -->
</form>

2. データ属性を活用しよう

<!-- 商品IDなどのデータを埋め込む -->
<button class="add-cart" data-product-id="12345" data-price="2980">
  カートに追加
</button>

<script>
document.querySelectorAll(".add-cart").forEach(btn => {
  btn.addEventListener("click", (e) => {
    const productId = e.target.dataset.productId;
    const price = e.target.dataset.price;
    console.log(`商品ID: ${productId}, 価格: ${price}`);
  });
});
</script>

3. アクセシビリティを忘れずに

<!-- アイコンだけのボタンには説明を -->
<button aria-label="メニューを開く">
  <i class="icon-menu"></i>
</button>

<!-- 無効化する時はaria-disabledも -->
<button disabled aria-disabled="true">送信</button>

エラー処理(これ大事!)

APIエラーの処理

const saveButton = document.getElementById("save");

saveButton.addEventListener("click", async () => {
  try {
    // ローディング表示
    saveButton.disabled = true;
    saveButton.textContent = "保存中...";

    // API呼び出し
    const response = await fetch("/api/save", {
      method: "POST",
      body: JSON.stringify({ data: "something" })
    });

    if (!response.ok) {
      throw new Error("保存に失敗しました");
    }

    // 成功
    saveButton.textContent = "保存しました!";

  } catch (error) {
    // エラー表示
    alert(error.message);
    saveButton.textContent = "もう一度試す";

  } finally {
    // ボタンを再度有効化
    setTimeout(() => {
      saveButton.disabled = false;
      saveButton.textContent = "保存";
    }, 2000);
  }
});

ポイント:

  • try-catchでエラーをキャッチ
  • ユーザーにフィードバックを返す
  • finallyで必ず後処理を実行

まとめ

JavaScriptのボタン実装、最初は「なんで動かないの?」の連続でした。

でも基本を押さえれば、意外とシンプルです:

  1. HTMLでボタンを作る(button要素推奨)
  2. addEventListenerでイベントを登録(魔法の呪文)
  3. エラー処理を忘れない(連打対策など)

私が3日悩んだのは、HTMLとJavaScriptの繋がりを理解してなかったから。

でも一度理解すれば、モーダル、タブ切り替え、フォーム送信など、いろんな機能が作れるようになります。

最初は簡単なalertから始めてOK。少しずつ複雑な処理に挑戦していけばいいんです。

ボタンはWebサイトの「顔」です。

ユーザーが最初に触る部分だから、しっかり作り込む価値があります。

今日作ったボタンが、明日は誰かの役に立つかもしれません。

さあ、まずは簡単なボタンから作ってみましょう!

共有:

著者について

とまだ

とまだ

フルスタックエンジニア

Learning Next の創設者。Ruby on Rails と React を中心に、プログラミング教育に情熱を注いでいます。初心者が楽しく学べる環境作りを目指しています。

著者の詳細を見る →