DOM0のイベントモデルでは、イベント発生時にはイベント ターゲット (イベントが発生した要素) のハンドラのみが実行されます。一方でDOM2では、DOM階層における上位の要素のハンドラにも、実行の機会が与えられます。
イベントは、次の3つの段階 (フェーズ) を経て伝播されます。
イベントを捕捉したハンドラはEvent.stopPropagation()で、そのイベントの伝播を停止できます。よってすべてのイベントフェーズが、つねに処理されるとは限りません。
また、イベントによってはバブリング (bubbling) しないものもあります。たとえば、
が、それです。
イベントフェーズ (event phase)
3.1 Event dispatch and DOM event flow - DOM Level 3 Events Specification
capture phase
(キャプチャリング)the event object must propagate through the target's ancestors from the defaultView to the target's parent. This phase is also known as the capturing phase.
Event listeners registered for this phase must handle the event before it reaches its target.
target phase
(ターゲット)the event object must arrive at the event object's event target. This phase is also known as the at-target phase.
Event listeners registered for this phase must handle the event once it has reached its target.
If the event type indicates that the event must not bubble, the event object must halt after completion of this phase.
bubble phase
(バブリング)the event object propagates through the target's ancestors in reverse order, starting with the target's parent and ending with the defaultView. This phase is also known as the bubbling phase.
Event listeners registered for this phase must handle the event after it has reached its target.
DOMレベル2のイベントは、addEventListener()によって登録できます。
target.addEventListener( type, listener [, useCapture ] );EventTarget.addEventListener - DOM | MDN
ここからは、このメソッドの第3引数useCaptureをtrueとした場合の挙動について検証していきます。
対象とする要素を、次のように定義します。
<div id="a1">A1 <div id="a2">A2 <div id="a3">A3 </div> </div> </div>
これは、ブラウザでは次のように表示されます。
そしてスクリプトを以下のように記述します。このスクリプトでは各要素にclickイベントを登録し、クリック時にその要素のIDがアラートで出力されるようにしています。
var a1 = document.getElementById( 'a1' ); var a2 = document.getElementById( 'a2' ); var a3 = document.getElementById( 'a3' ); a1.addEventListener( 'click', OnClick, false ); a2.addEventListener( 'click', OnClick, false ); a3.addEventListener( 'click', OnClick, false ); function OnClick( event ) { var e = event || window.event; alert( e.currentTarget.id ); }
a1、a2、a3のすべての要素のハンドラのuseCaptureをfalse、つまり既定値に設定すると、
の順にアラートが表示されます。つまり、DOM階層の下位から上位に向かってイベントが捕捉されます。次に、a2のハンドラの引数をtrueにしてみます。
a1.addEventListener( 'click', OnClick, false ); a2.addEventListener( 'click', OnClick, true ); a3.addEventListener( 'click', OnClick, false );
そうすると、このようにa2の要素が先に捕捉されるようになります。さらにa1のハンドラもtrueにすると、
a1.addEventListener( 'click', OnClick, true ); a2.addEventListener( 'click', OnClick, true ); a3.addEventListener( 'click', OnClick, false );
のようになり、a1がa2よりも先に捕捉されるようになりました。つまりuseCaptureの値によって、
のように捕捉される順番が決定されることがわかります。
ハンドラがEvent.stopPropagation()を呼び出すと、それ以降イベントは伝播しなくなります。これを検証するため前項のコードを一部修正し、a2のハンドラがstopPropagation()を呼び出すようにしてみます。
a1.addEventListener( 'click', OnClick, false ); a2.addEventListener( 'click', OnClick, false ); a3.addEventListener( 'click', OnClick, false ); function OnClick( event ) { var e = event || window.event; alert( e.currentTarget.id ); if( e.currentTarget.id == 'a2' ) { e.stopPropagation(); } }
そうすると、
このようになり、a2の要素でイベント伝播が停止しているのがわかります。次に、a2のハンドラのuseCaptureをtrueにしてみます。
a1.addEventListener( 'click', OnClick, false ); a2.addEventListener( 'click', OnClick, true ); a3.addEventListener( 'click', OnClick, false );
前述したように、useCaptureをtrueとすると優先して捕捉されるようになります。このとき伝播を停止するとそれ以降のハンドラでは捕捉されなくなり、結果としてこの例ではa2の要素しか呼ばれなくなっています。
ちなみにa1のハンドラもtrueとすると、
a1.addEventListener( 'click', OnClick, true ); a2.addEventListener( 'click', OnClick, true ); a3.addEventListener( 'click', OnClick, false );
のような結果となります。これはDOM階層の上位が優先して捕捉され、かつa2で伝播が停止されているためです。