キャプチャリングとバブリング Firefox編
サイ本の17.2に詳しく書いてありますが、JavaScriptのDOM レベル2イベントでは、
イベント伝播は3つの段階で構成されます。
- キャプチャリングフェーズ
- ターゲットノード自身でのイベントハンドラの実行
- バブリングフェーズ
上図のように3つ入れ子になった要素の、
外側から2つ目の要素をクリックした場合を例にとると、
キャプチャリングフェーズでdocument→外側の要素→と伝播していき、
ターゲットノードでのイベントハンドラ(あれば)が実行され、
バブリングフェーズで→外側の要素→documentと伝播していきます。
DOMレベル0のイベントではターゲットノードでしか
イベントハンドラの実行は出来ませんが、
DOMレベル2イベントではキャプチャリングフェーズ・バブリングフェーズでも
イベントハンドラの実行が出来ます。
検証用HTML
確認のためにdivの親子をそれぞれクリックして、
イベントのlogを吐かせるだけの簡単なHTML+JavaScriptを用意しました。
このHTMLをhtmlファイルにして開くときは、必ずFirefoxで開いてください。
でないと、説明どおりの動作はしません。
<html> <head> <script> (function(func){ var temp = []; log = function(s){ temp.push(s); }; function init(){ if(typeof document.readyState != 'undefined' && document.readyState != 'loaded' && document.readyState != 'complete'){ setTimeout(arguments.callee,100); return; } var div = document.createElement('div'); div.id = 'log'; for(var i = 0;i < temp.length;i++){ div.appendChild( document.createTextNode(temp[i]) ); div.appendChild( document.createElement('br') ); } document.body.appendChild(div); log = function(s){ div.appendChild( document.createTextNode(s) ); div.appendChild( document.createElement('br') ); }; func(); } if(typeof document.readyState == 'undefined' && document.addEventListener){ document.addEventListener('DOMContentLoaded',init, false ); }else{ init(); } })(function(){ /* ロード時に実行されるコード */ document.getElementById('outer1').addEventListener('click',function(e){eventLog(e);},true); document.getElementById('inner1').addEventListener('click',function(e){eventLog(e);},true); document.getElementById('outer2').addEventListener('click',function(e){eventLog(e);},false); document.getElementById('inner2').addEventListener('click',function(e){eventLog(e);},false); document.getElementById('outer3').addEventListener('click',function(e){eventLog(e);},true); document.getElementById('inner3').addEventListener('click',function(e){eventLog(e);},true); document.getElementById('outer3').addEventListener('click',function(e){eventLog(e);},false); document.getElementById('inner3').addEventListener('click',function(e){eventLog(e);},false); document.getElementById('outer4').addEventListener('click',function(e){eventLog(e);e.stopPropagation();},true); document.getElementById('inner4').addEventListener('click',function(e){eventLog(e);},true); document.getElementById('outer5').addEventListener('click',function(e){eventLog(e);},false); document.getElementById('inner5').addEventListener('click',function(e){eventLog(e);e.stopPropagation();},false); document.getElementById('hoge').addEventListener('click',function(e){eventLog(e);},false); document.getElementById('outer6').addEventListener('click',function(e){eventLog(e);e.stopPropagation();},false); document.getElementById('inner6').addEventListener('click',function(e){eventLog(e);},false); }); function eventLog(e){ log([ ['eventPhase',e.eventPhase].join(':'), ['target',e.target['id']].join(':'), ['currentTarget',e.currentTarget['id']].join(':') ].join()); } </script> <style> .outer{ border:solid; width:40px; height:40px; } .inner{ border:solid; width:20px; height:20px; } </style> </head> <body> (1)内外共にuseCaptureにtrueを設定 <div id="outer1" class="outer"> <div id="inner1" class="inner"> </div> </div> (2)内外共にuseCaptureにfalseを設定 <div id="outer2" class="outer"> <div id="inner2" class="inner"> </div> </div> (3)内外共に、useCaptureにtrue・false両方でイベントハンドラを設定 <div id="outer3" class="outer"> <div id="inner3" class="inner"> </div> </div> (4)内外共にuseCaptureにtrueを設定、外側でEvent.stopPropagation()を実行する <div id="outer4" class="outer"> <div id="inner4" class="inner"> </div> </div> (5)内外共にuseCaptureにfalseを設定、内側でEvent.stopPropagation()を実行する <div id="outer5" class="outer"> <div id="inner5" class="inner"> </div> </div> (6)3つすべてでuseCaptureにfalseを設定、外側から2番目でEvent.stopPropagation()を実行する <div id="hoge" style="border:solid;width:80px;height:80px;"> <div id="outer6" class="outer"> <div id="inner6" class="inner"> </div> </div> </div> </body> </html>
ログの見方は以下の通りです。
eventPhase:${イベントフェーズ},target:${ターゲットノードのid},currentTarget:${現在イベントが処理されているノードのid} 注:イベントフェーズは、1・キャプチャリング、2・ターゲットノード、3・バブリング
イベントハンドラ設定用のメソッドは以下のようになっていて、
useCaptureにtrueを設定するとキャプチャリングフェーズにイベントハンドラが実行、
falseを設定するとバブリングフェーズにイベントハンドラが実行されます。
addEventListener(type,listener,useCapture)
(1)内外共にuseCaptureにtrueを設定
この場合ターゲットノードの場合とキャプチャリングフェーズにイベントハンドラが実行されます。
外側のdiv(id=outer1)をクリックすると、以下のように表示されます。
eventPhase:2,target:outer1,currentTarget:outer1
内側のdiv(id=inner1)をクリックした場合は、以下のように表示されます。
eventPhase:1,target:inner1,currentTarget:outer1 eventPhase:2,target:inner1,currentTarget:inner1
外側、内側という順にイベントハンドラが実行されていること、
外側のdivに設定したイベントハンドラ実行時は、
eventPhase:1=キャプチャリングフェーズであることが分かります。
(2)内外共にuseCaptureにfalseを設定
この場合場合ターゲットノードの場合とバブリングフェーズにイベントハンドラが実行されます。
外側のdiv(id=outer2)をクリックすると、以下のように表示されます。
eventPhase:2,target:outer2,currentTarget:outer2
内側のdiv(id=inner2)をクリックした場合は、以下のように表示されます。
eventPhase:2,target:inner2,currentTarget:inner2 eventPhase:3,target:inner2,currentTarget:outer2
内側、外側という順にイベントハンドラが実行されていること、
外側のdivに設定したイベントハンドラ実行時は、
eventPhase:3=バブリングフェーズであることが分かります。
(3)内外共に、useCaptureにtrue・false両方でイベントハンドラを設定
外側のdiv(id=outer3)をクリックすると、以下のように表示されます。
eventPhase:2,target:outer3,currentTarget:outer3 eventPhase:2,target:outer3,currentTarget:outer3
内側のdiv(id=inner3)をクリックした場合は、以下のように表示されます。
eventPhase:1,target:inner3,currentTarget:outer3 eventPhase:2,target:inner3,currentTarget:inner3 eventPhase:2,target:inner3,currentTarget:inner3 eventPhase:3,target:inner3,currentTarget:outer3
前述の図のように外→内→外というようにイベントが伝播していることが分かります。
Event.stopPropagation()
イベントハンドラの引数であるEventオブジェクトのstopPropagationメソッドを実行すると、
それ以降のイベントの伝播を停止させられます。
どのフェーズであってもそのイベントハンドラ以降の伝播は停止します。
(4)内外共にuseCaptureにtrueを設定、外側でEvent.stopPropagation()を実行する
外側のdiv(id=outer4)をクリックすると、以下のように表示されます。
eventPhase:2,target:outer4,currentTarget:outer4
内側のdiv(id=inner4)をクリックした場合は、以下のように表示されます。
eventPhase:1,target:inner4,currentTarget:outer4
内側のdivをクリックした場合、外側のdivに設定されたイベントハンドラが
キャプチャリングフェーズで実行され、そこでイベントの伝播が止まっているのが分かります。
((1)と要比較)
(5)内外共にuseCaptureにfalseを設定、内側でEvent.stopPropagation()を実行する
外側のdiv(id=outer5)をクリックすると、以下のように表示されます。
eventPhase:2,target:outer5,currentTarget:outer5
内側のdiv(id=inner5)をクリックした場合は、以下のように表示されます。
eventPhase:2,target:inner5,currentTarget:inner5
内側のdivをクリックした場合、ターゲットノードでイベントハンドラが実行され、
イベント伝播が止まり、外側のdivのイベントハンドラが実行されません。
((2)と要比較)
(6)3つ入れ子のdivすべてでuseCaptureにfalseを設定、外側から2番目でEvent.stopPropagation()を実行する
一番外側のdiv(id=hoge)をクリックすると、以下のように表示されます。
eventPhase:2,target:hoge,currentTarget:hoge
間のdiv(id=outer6)、内側のdiv(id=inner6)をクリックした場合は、
それぞれ以下のように表示されます。
eventPhase:2,target:outer6,currentTarget:outer6
eventPhase:2,target:inner6,currentTarget:inner6 eventPhase:3,target:inner6,currentTarget:outer6
内側のdivから間のdivまではバブリングしてますが、
そこでイベント伝播は止まり、一番外側のイベントハンドラは実行されません。