JavaScriptではイベントが発生したらイベントハンドラーが呼び出されて処理が走るようになっているが、実はイベントリスナーはイベント発生元の要素だけで実行されるわけではない。
イベント発生元の要素の親要素へ順にイベントが伝搬していき、親要素でも同じイベントが発生する。
イベントの伝番はキャプチャフェーズ、ターゲットフェーズ、バブリングフェーズという3つのフェーズに分かれており、その過程で対応するイベントリスナーが存在すれば、それらも順番に実行されるという仕組みになっている。
以下のhtmlがあってbutton要素でクリックイベントが発生したとする。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>サンプル</title>
<script type="text/javascript" src="sample.js"></script>
</head>
<body>
<div id="outer">
<p>outer要素</p>
<button id="inner">ボタン</button>
</div>
</body>
</html>
ボタンがクリックされたときのこのhtmlのイベントの伝番を図で表すと以下のようになる。
まず①キャプチャフェーズでwindowオブジェクトからDOMツリーをたどって、下位の要素にイベントを伝番していく。
そしてイベントの発生元の要素を特定する。ここが②ターゲットフェーズに当たる。
最後の③バブリングフェーズでは、イベント発生元からルート要素に向かってイベントが伝番していく。同じイベントリスナーがあれば処理を実行する。
windowオブジェクトまでたどり着いたらイベントの伝番は終了する。
ちなみにバブリングフェーズはイベントが親要素に伝番されていく様子が泡が浮かび上がる様子に似ていることから呼ばれてるようになったという。
実際にイベントを発生させてみる。
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('inner').addEventListener('click', function() {
window.alert('innerリスナーが発生!');
}, false);
document.getElementById('outer').addEventListener('click', function() {
window.alert('outerリスナーが発生!');
}, false);
}, false);
入れ子関係であるdiv要素とbutton要素に対して、clickイベントリスナーが設定されている。
このときにbutton要素をクリックすると以下の順で処理が実行される。
1.アラート(innerリスナー1が発生!)表示処理
2.アラート(outerリスナーが発生!)表示処理
実際の画面↓
↓
まずはwindowオブジェクトから下位要素にイベントを伝番していく。(キャプチフェーズ)
その次にbuttonでイベントが発生しているのを特定する。(ターゲットフェーズ)
その後、イベント発生元であるbutton要素から、上位要素に向かって順にイベントリスナーが実行されている。(バブリングフェーズ)
ちなみに同じ要素に対して複数のイベントリスナーが設定されている場合には記述順に処理が実行されていく。
当然だが、この状態でdiv要素をクリックするとdiv要素のイベントリスナーの処理だけが実行される。
addEventListenerの第3引数をtrueに変えてbuttonをクリックすると、上位要素からイベント発生元に向かってイベントリスナーの処理が実行される。
↓
これはキャプチャフェーズで処理が実行されているということを表している。
イベントの伝番のキャンセル方法
通常だと上記のようにイベントが伝番されて順に処理が実行されていくが、伝番されたくない場合もある。
このような場合はstopPropagationメソッドを使用する。
以下はbuttonをクリックしたときid="inner"の処理のみ実行するコードである。
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('inner').addEventListener('click', function(e) {
window.alert('innerリスナーが発生!');
e.stopPropagation();
}, false);
document.getElementById('outer').addEventListener('click', function() {
window.alert('outerリスナーが発生!');
}, false);
}, false);
window.alert('innerリスナーが発生!')の後にe.stopPropagation()を実行して上位要素のイベントリスナーの処理を実行させないようにしている。
実際にボタンをクリックすると表示されるアラートは以下のものだけ。
親ノードへのバブリングがキャンセルされている。逆にキャプチャフェーズでイベントリスナーの処理を実行する場合は上位要素のイベントリスナーの処理の中でe.stopPropagation()を実行すれば下位要素へのイベントの伝番をキャンセルできる。
■同じ要素にある複数のイベントリスナーへの伝番をキャンセルしたい場合
同じ要素に複数のイベントリスナーがあって、それらへの伝番をキャンセルしたい場合はstopImmediatePropagationメソッドを使用する。
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('inner').addEventListener('click', function(e) {
window.alert('innerリスナーが発生!');
e.stopImmediatePropagation();
}, false);
document.getElementById('inner').addEventListener('click', function(e) {
window.alert('innerリスナー2が発生!');
}, false);
document.getElementById('outer').addEventListener('click', function() {
window.alert('outerリスナーが発生!');
}, false);
}, false);
上記コードの場合、buttonをクリックしたときに通常なら下記2つのイベントリスナーの処理が実行される。
①アラート('innerリスナーが発生!')表示
②アラート('innerリスナー2が発生!')表示
しかし、e.stopImmediatePropagation()を実行することで「アラート('innerリスナーが発生!')」のみを表示させることができる。
window.alert('innerリスナー2が発生!')が実行されるイベントリスナーへの伝番がキャンセルされた。