バブリングとキャプチャリング
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です」の後に「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があっても動作は同じです。
上記は、以下のようになります。
画像をクリックすると、「div内です」の後に「スペードのAです」がアラートで表示されます。つまり、先ほどとは逆の順になっています。
なお、この例でx.addEventListener("click", func1);を追加すると(trueがない)、「div内です」→「スペードのAです」→「div内です」の順にアラートが表示されます。このように、同じ要素で、キャプチャリングフェーズでもバブリングフェーズでも発火させることができます。
イベント伝播停止
イベントの伝播をさせたくない時は、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プロパティの利用例です。
<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プロパティの利用例です。
<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プロパティによって、クリックした要素を取得しています。
上記を実行すると、以下になります。
画像をクリックすると、各画像に対応したalt属性の内容がアラートで表示されると思います。
これは、子要素の画像をクリックしても、親要素で登録したイベントが発火するためです。
前のページ「イベント処理」