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のボタン実装、最初は「なんで動かないの?」の連続でした。
でも基本を押さえれば、意外とシンプルです:
- HTMLでボタンを作る(button要素推奨)
- addEventListenerでイベントを登録(魔法の呪文)
- エラー処理を忘れない(連打対策など)
私が3日悩んだのは、HTMLとJavaScriptの繋がりを理解してなかったから。
でも一度理解すれば、モーダル、タブ切り替え、フォーム送信など、いろんな機能が作れるようになります。
最初は簡単なalertから始めてOK。少しずつ複雑な処理に挑戦していけばいいんです。
ボタンはWebサイトの「顔」です。
ユーザーが最初に触る部分だから、しっかり作り込む価値があります。
今日作ったボタンが、明日は誰かの役に立つかもしれません。
さあ、まずは簡単なボタンから作ってみましょう!
著者について

とまだ
フルスタックエンジニア
Learning Next の創設者。Ruby on Rails と React を中心に、プログラミング教育に情熱を注いでいます。初心者が楽しく学べる環境作りを目指しています。
著者の詳細を見る →