バブリングとキャプチャリング

JavaScriptでイベントが発火すると、他のイベントも発火することがあります。

本ページでは、バブリングとキャプチャリングについて説明します。

イベントの伝播

イベントが発火した場合、親要素のイベントも発火します。これは、以下のようにイベントが伝播するためです。

イベントが伝播する様子

イベントの伝播順は、以下のとおりです。

キャプチャリングフェーズ
最上位の要素から、イベント発生元の要素(実際にクリックなどが発生した要素)に向かって順に発火します。
ターゲットフェーズ
イベント発生元の要素で登録されたイベントが、すべて発火します。
バブリングフェーズ
最上位の要素に向かって、順に発火します。

clickとdblclickなど、イベントの種類が違うと伝播しません。また、focusやblurなど一部のイベントは、伝播しません。

バブリングフェーズで発火させる

イベントは、デフォルトではバブリングフェーズの時に発火します。以下は、例です。

【バブリングフェーズの時に発火する例】
<div id="div1">
<image src="11.png" alt="スペードA" id="image1">
</div>

<script>
function func1() {
  alert("div内です");
}
function func2() {
  alert("スペードのAです");
}

const x = document.querySelector("#div1");
const y = document.querySelector("#image1");
x.addEventListener("click", func1);
y.addEventListener("click", func2);
</script>

div要素を対象にしたイベントリスナーと、img要素を対象にしたイベントリスナーを登録しています。div要素は、imgの親要素です。

上記は、以下のようになります。

スペードA

画像をクリックすると、「スペードのAです」の後に「div内です」がアラートで表示されます。つまり、divをターゲットにしたイベントは、キャプチャリングフェーズでは発火せず、バブリングフェーズでだけ発火しています(ターゲット→親の順に発火している)。

キャプチャリングフェーズで発火させる

バブリングフェーズでの発火から、キャプチャリングフェーズの発火に変えることができます。以下は、例です。

【キャプチャリングフェーズの時に発火する例】
<div id="div1">
<image src="11.png" alt="スペードA" id="image1">
</div>

<script>
function func1() {
  alert("div内です");
}
function func2() {
  alert("スペードのAです");
}

const x = document.querySelector("#div1");
const y = document.querySelector("#image1");
x.addEventListener("click", func1, true);
y.addEventListener("click", func2);
</script>

イベントリスナーの最後に、オプションのtrueが記述されています。これで、キャプチャリングフェーズでの発火になります。オプションのデフォルトはfalseで、バブリングフェーズでの発火です。

y.addEventListenerにtrueがないのは、ターゲットフェーズは必ず発生するためです。この例で言えば、trueがあっても動作は同じです。

上記は、以下のようになります。

スペードA

画像をクリックすると、「div内です」の後に「スペードのAです」がアラートで表示されます。つまり、先ほどとは逆の順になっています。

なお、この例でx.addEventListener("click", func1);を追加すると(trueがない)、「div内です」→「スペードのAです」→「div内です」の順にアラートが表示されます。このように、同じ要素で、キャプチャリングフェーズでもバブリングフェーズでも発火させることができます。

イベント伝播停止

イベントの伝播をさせたくない時は、stopPropagationメソッドを使います。以下は、例です。

【stopPropagationメソッドの利用例】
<div id="div1">
<image src="11.png" alt="スペードA" id="image1">
</div>

<script>
function func1() {
  alert("div内です");
}
function func2(event) {
  alert("スペードのAです");
  event.stopPropagation();
}

const x = document.querySelector("#div1");
const y = document.querySelector("#image1");
x.addEventListener("click", func1);
y.addEventListener("click", func2);
</script>

上記により、画像をクリックすると「スペードのAです」だけがアラートで表示されます。イベントが伝播しないため、「div内です」は表示されません。

targetプロパティ

イベント発生元の要素は、イベントオブジェクトのtargetプロパティで取得できます。以下は、targetプロパティの利用例です。

【targetプロパティの利用例】
<div id="div1">
<p>テスト1</p>
<p id="p1">テスト2</p>
</div>

<script>
function func1(event) {
  event.target.style.border = "solid";
}
function func2(event) {
  event.target.style.backgroundColor = "yellow";
}

const x = document.querySelector("#div1");
const y = document.querySelector("#p1");
x.addEventListener("click", func1);
y.addEventListener("click", func2);
</script>

上記は、divをクリックするとfunc1を呼び出して枠線を、IDがp1のp要素をクリックするとfunc2を呼び出して背景を黄色にするようイベントリスナーを登録しています。

また、divが親要素なので、IDがp1の要素をクリックするとfunc1もfunc2も実行されます。この時、どちらもtargetはIDがp1の要素です(イベントの発生がIDがp1の要素のため)。

上記を実行すると、以下になります。

テスト1

テスト2

「テスト2」をクリックすると、背景が黄色になって枠線も表示されると思います。つまり、func1でもfunc2でも、targetはイベント発生元の要素ということです(divの周りに枠線が表示されるのではない)。

currentTargetプロパティ

イベントハンドラーを呼び出す要因となった要素は、イベントオブジェクトのcurrentTargetプロパティで取得できます。以下は、currentTargetプロパティの利用例です。

【currentTargetプロパティの利用例】
<div id="div1">
<p>テスト1</p>
<p id="p1">テスト2</p>
</div>

<script>
function func1(event) {
  event.currentTarget.style.border = "solid";
}
function func2(event) {
  event.currentTarget.style.backgroundColor = "yellow";
}

const x = document.querySelector("#div1");
const y = document.querySelector("#p1");
x.addEventListener("click", func1);
y.addEventListener("click", func2);
</script>

先ほどのtargetの利用例からは、targetをcurrentTargetに変えただけです。

上記は、idがp1の要素をクリックするとfunc1もfunc2も実行されますが、func1のcurrentTargetはIDがdiv1の要素、func2のcurrentTargetはIDがp1の要素です。

上記を実行すると、以下になります。

テスト1

テスト2

「テスト2」をクリックすると、段落の背景が黄色になりますが、枠線は先ほどと違ってdiv要素の周りに表示されると思います。つまり、currentTargetは、func1とfunc2で異なっているということです。

なお、currentTargetはthisと同じです。このため、this.style.border = "solid"としても、結果は同じです。

イベントの委譲

イベントが伝播するため、複数の要素で同じ種類のイベントを作成したい場合、親要素をターゲットに作成するだけで済みます。これを、イベントの委譲と言います。以下は、例です。

【イベント委譲の例】
<div id="div1">
<img src="11.png" alt="スペードA">
<img src="12.png" alt="スペード2">
<img src="13.png" alt="スペード3">
</div>

<script>
function func1(event) {
  alert(event.target.alt);
}

const x = document.querySelector("#div1");
x.addEventListener("click", func1);
</script>

上記は、div要素をターゲットにイベントを作成していますが、画像をクリックした場合でも伝播によってfunc1が実行されます。また、targetプロパティによって、クリックした要素を取得しています。

上記を実行すると、以下になります。

スペードA スペード2 スペード3

画像をクリックすると、各画像に対応したalt属性の内容がアラートで表示されると思います。

これは、子要素の画像をクリックしても、親要素で登録したイベントが発火するためです。

前のページイベント処理