addEventListener() で登録したコールバック関数は、呼び出されるときに「イベントオブジェクト」を1つ受け取ります。慣例的に event や e という名前で受け取るこのオブジェクトには、どの要素で何が起きたのかという情報が詰まっており、リンクの遷移やフォーム送信といったブラウザ標準の動きを止めることもできます。この記事では、イベントオブジェクトの主要なプロパティと、preventDefault() と stopPropagation() という2つのメソッドの違い、そしてイベントが親要素へ伝わる「バブリング」の仕組みまでを、動くデモとともに解説します。
目次
コールバックが受け取るイベントオブジェクトとは
クリックやキー入力などが起きると、ブラウザはその「出来事」を表すオブジェクトを作り、登録された関数へ第1引数として渡します。これがイベントオブジェクトです。受け取る側の引数名は自由ですが、event と書くか、短く e と書くのが一般的です。次のように書けば、クリックされた瞬間の情報を event から取り出せます。
const button = document.querySelector("button");
button.addEventListener("click", (event) => {
// event にクリックの情報が入っている
console.log(event.type); // "click"
console.log(event.target); // クリックされた要素
});
引数を書かなくてもリスナーは動きますが、これから紹介する preventDefault() や event.target を使うには、必ずこの引数を受け取る必要があります。まずはよく使うプロパティを見ていきましょう。
よく使うイベントオブジェクトのプロパティ
イベントオブジェクトには多くのプロパティがありますが、まず押さえておきたいのは次の4つです。とくに target と currentTarget は名前が似ていて混同しやすいので、後ほど詳しく説明します。
| プロパティ | 取得できる内容 |
|---|---|
event.type | イベントの種類を表す文字列("click"、"submit" など) |
event.target | 実際にイベントが発生した要素(例: クリックされた要素そのもの) |
event.currentTarget | リスナーを登録した要素(this と同じ要素) |
event.key | キーイベントで押されたキーの値("Enter" など。キー系イベントのみ) |
下のデモは、3つのボタンをまとめている親要素(.panel)に1つだけリスナーを付け、event.target で「実際に押されたボタン」を特定して表示しています。どのボタンを押しても、そのボタンの情報が event.target から取れることを確かめてみてください。
<div class="panel">
<button data-name="保存">保存</button>
<button data-name="削除">削除</button>
<button data-name="共有">共有</button>
</div>
<p>クリックした要素: <span id="log">(ボタンを押してください)</span></p>
.panel {
display: flex;
gap: 8px;
margin-bottom: 12px;
}
button {
padding: 8px 16px;
cursor: pointer;
}
p {
font-family: sans-serif;
}
#log {
font-weight: bold;
color: #007bff;
}
const panel = document.querySelector(".panel");
const log = document.getElementById("log");
// パネル自身に1つだけリスナーを付ける(イベント委譲)
panel.addEventListener("click", (event) => {
// event.target は実際にクリックされた要素
const button = event.target;
// data-name 属性からラベルを取り出して表示する
log.textContent = `${button.dataset.name}(type: ${event.type})`;
});
target と currentTarget の違い
この2つは似ていますが役割が違います。event.currentTarget はリスナーを登録した要素で、上のデモでは常に .panel です。一方 event.target は実際にイベントが起きた要素で、押したボタンによって「保存」「削除」と変わります。親要素にまとめてリスナーを付けたとき、どの子要素が操作されたのかを知りたい場面では event.target を使う、と覚えておくとよいでしょう。アロー関数ではなく function で書いた場合、currentTarget は this と同じ要素を指します。
preventDefault で既定の動作を止める
ブラウザには、要素ごとに決められた「標準の動き」があります。リンク(<a>)をクリックすればページが遷移し、送信ボタンを押せばフォームが送信されてページが再読み込みされます。event.preventDefault() は、この標準の動きだけをキャンセルするメソッドです。JavaScript で独自の処理をしたいときに、ブラウザの既定動作が邪魔にならないようにできます。
下のデモは、フォームの submit イベントで preventDefault() を呼び、ページを再読み込みさせずに入力内容を画面に表示します。preventDefault() を消すと、送信のたびにページが再読み込みされてしまうことも想像してみてください。
<form id="form">
<label>お名前: <input type="text" id="name" required></label>
<button type="submit">送信</button>
</form>
<p id="result"></p>
form {
font-family: sans-serif;
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
input {
padding: 6px 8px;
}
button {
padding: 8px 16px;
cursor: pointer;
}
#result {
font-family: sans-serif;
font-weight: bold;
color: #2e7d32;
}
const form = document.getElementById("form");
const nameInput = document.getElementById("name");
const result = document.getElementById("result");
form.addEventListener("submit", (event) => {
// 既定の送信(ページの再読み込み)を止める
event.preventDefault();
// 送信の代わりに、入力内容を画面に表示する
result.textContent = `${nameInput.value} さん、送信を受け付けました。`;
});
フォーム以外でも、リンクをクリックしたときの遷移を止めて別の処理をしたい場合や、チェックボックスの標準の切り替えを止めたい場合などに使います。ただし、止めるのはあくまで「既定の動作」だけです。次に説明するイベントの伝播は、preventDefault() では止まりません。
イベントの伝播(バブリング)とは
要素をクリックすると、そのイベントはクリックされた要素から、親・祖先へと順番に伝わっていきます。これをイベントの伝播、とくに下から上へ伝わる様子から「バブリング(bubbling)」と呼びます。たとえば <div> の中の <button> をクリックすると、まずボタンのリスナーが呼ばれ、続いて外側の <div> のリスナーも呼ばれます。親要素にリスナーを付けているだけなのに、中の要素をクリックしても反応するのはこのためです。
このバブリングを止めたいときに使うのが、次に説明する stopPropagation() です。
stopPropagation で伝播を止める
event.stopPropagation() は、イベントがそれ以上親要素へ伝わらないようにするメソッドです。子要素のリスナーの中で呼び出すと、その先の親要素のリスナーは実行されなくなります。下のデモでは、外側(outer)と内側(inner)の両方にクリックのリスナーを付けています。チェックボックスをオフのまま子要素をクリックすると、子と親が両方反応します(バブリング)。チェックを入れると子の中で stopPropagation() が呼ばれ、親には伝わらなくなります。
<div id="outer">
親(outer)
<div id="inner">子(inner)をクリック</div>
</div>
<label><input type="checkbox" id="stop"> 子の stopPropagation() を有効にする</label>
<ul id="log"></ul>
#outer {
padding: 20px;
background: #e3f2fd;
border: 2px solid #64b5f6;
border-radius: 8px;
font-family: sans-serif;
cursor: pointer;
}
#inner {
margin-top: 12px;
padding: 16px;
background: #fff3e0;
border: 2px solid #ffb74d;
border-radius: 8px;
}
label {
display: block;
margin: 12px 0;
font-family: sans-serif;
}
#log {
font-family: monospace;
background: #263238;
color: #aed581;
padding: 12px 24px;
border-radius: 8px;
min-height: 24px;
}
const outer = document.getElementById("outer");
const inner = document.getElementById("inner");
const stop = document.getElementById("stop");
const log = document.getElementById("log");
// 子要素のクリック
inner.addEventListener("click", (event) => {
log.innerHTML += "<li>子(inner)が反応</li>";
// チェックが入っていれば、親への伝播を止める
if (stop.checked) {
event.stopPropagation();
log.innerHTML += "<li>→ stopPropagation() で伝播を停止</li>";
}
});
// 親要素のクリック(バブリングで届く)
outer.addEventListener("click", () => {
log.innerHTML += "<li>親(outer)が反応</li>";
});
preventDefault と stopPropagation の違い
名前が似ているため混同されがちですが、この2つはまったく別の役割です。preventDefault() が止めるのは「ブラウザの既定動作」、stopPropagation() が止めるのは「イベントの親要素への伝播」です。一方を呼んでももう一方は起こるので、目的に応じて使い分けます。
| メソッド | 止めるもの | 使う場面の例 |
|---|---|---|
preventDefault() | ブラウザの既定動作(遷移・送信など) | リンクの遷移やフォーム送信を止めて独自処理をする |
stopPropagation() | 親要素へのイベント伝播(バブリング) | 子の操作を親のリスナーに反応させたくない |
たとえばリンクの遷移を止めたいだけなら preventDefault() を、モーダルの内側をクリックしても閉じてほしくない(親に付けた「クリックで閉じる」処理を反応させたくない)なら stopPropagation() を使います。両方を同時に呼ぶこともできますが、必要なほうだけを呼ぶのが基本です。
バブリングを活かすイベント委譲
バブリングは止めるだけのものではなく、うまく利用すると便利です。子要素ひとつひとつにリスナーを付ける代わりに、親要素にまとめて1つだけリスナーを付け、event.target でどの子が操作されたかを判定する書き方を「イベント委譲(イベントデリゲーション)」と呼びます。この記事の最初のボタンのデモが、まさにその例です。.panel に1つリスナーを付けるだけで、中の3つのボタンすべてに対応できています。
const list = document.querySelector("ul");
list.addEventListener("click", (event) => {
// クリックされたのが li のときだけ処理する
if (event.target.tagName === "LI") {
console.log(event.target.textContent);
}
});
この方法なら、あとから li を追加してもリスナーを付け直す必要がありません。項目が動的に増減するリストで特に効果を発揮します。イベント委譲はバブリングという仕組みがあって初めて成り立つ、という点を意識しておくと理解が深まります。
思ったとおりに止まらないときの確認
引数を受け取り忘れていないか
preventDefault() や target が使えないときは、コールバックの引数(event)を書き忘れていないか確認してください。addEventListener("submit", () => { ... }) のように引数を省略すると、関数の中で event を参照できず、event is not defined というエラーになります。
preventDefault で伝播まで止めようとしていないか
「リンクをクリックしても親の処理が動いてしまう」ようなとき、preventDefault() を呼んでも伝播は止まりません。preventDefault() は遷移や送信といった既定動作を止めるだけで、親要素のリスナーは通常どおり呼ばれます。伝播を止めたいのであれば stopPropagation() を使ってください。逆に、伝播だけ止めても既定動作(リンクの遷移など)はそのまま起こります。
event.target が思った要素にならないとき
ボタンの中にアイコン用の <span> などを入れていると、その部分をクリックしたときに event.target が span になることがあります。「登録した要素」を確実に扱いたい場合は event.currentTarget を使うか、event.target.closest("button") のように目的の要素まで遡ると安定します。
まとめ
イベントオブジェクトを理解すると、イベント処理の自由度が大きく上がります。要点を整理します。
- コールバックの第1引数(
event/e)にイベントの情報が入る。 event.targetは実際に起きた要素、event.currentTargetは登録した要素。preventDefault()はブラウザの既定動作(遷移・送信)を止める。stopPropagation()は親要素へのイベント伝播(バブリング)を止める。- バブリングを利用した書き方がイベント委譲。親に1つだけリスナーを付けられる。
まずはこの記事のデモを実際に動かし、チェックボックスの有無で挙動がどう変わるかを見てみてください。preventDefault() と stopPropagation() の役割の違いが体感でつかめると、フォームやモーダルの実装で迷わなくなります。