MENU

JavaScriptDOM関数・メソッドaddeventlistener

【JavaScript】 addEventListener()の第三引数useCaptureの謎

更新日:2020/07/29

 

 

JavaScriptでDOM要素を操作する場合、addEventListener()メソッドを使用します。
このメソッドの第三引数にtrueをセットしているコードをよく目にします。
しかしtrueでもfalseでも同じように動作するので、どんな意味があるのかわからない人も多いのではないでしょうか。

 

そこで今回は、addEventListener()メソッドの第三引数についてお伝えします。

 

useCaptureの意味

 

addEventListenerの第三引数useCaptureは、trueまたはfalseの真偽値で指定します。

 

trueのとき、windowからdocument、body、とたどってイベントが発生した要素に行き着くキャプチャフェーズでのイベント通知が有効になります。
falseのときは、イベントが発生した要素からwindowへと行き着くバブリングフェーズでのイベント通知が有効になります。

 

DOM イベントフェーズ

 

これだけではわかりにくいので、順を追って解説します。

useCaptureの意味がないケース

 

次のようなhtmlでクリックイベントを捕捉してみます。

 

html

 

<div id="div1"  >
    <div id="div2"  >
        <div id="div3"  style="width:100px;height:100px;border:1px solid #555">

        </div>
    </div>
</div>

 

上のhtmlをブラウザで表示すると、下のような3つのdiv要素が重なり合った図形になります。

 

 

JavaScriptで、div3にクリックイベントを登録します。

 

JavaScript

 

window.addEventListener( "DOMContentLoaded" , ()=> {

    const click = e =>{
        console.log( "click:" + e.currentTarget.id );
    }
    document.getElementById("div3").addEventListener("click",click);
    
});

 

addEventListenerの第三引数が指定されていないので、デフォルトとしてfalseが有効になります。

 

falseはバブリングフェーズなので前項(useCaptureの意味)を読むと、クリックされたdiv3からdiv2、div1とイベントが発生すると受け取る人がいるかもしれません。

 

実行結果は次のようになります。

 

実行結果

 


click:div3

 

div3で一度イベントを捕捉するのみです。

 

では、addEventListenerの第三引数useCaptureをtrueにしてみます。

 

addEventListenerの第三引数useCaptureをtrueに変更

 


document.getElementById("div3").addEventListener("click",click, true );

 

キャプチャフェーズでのイベント通知が有効になったので、windowからdiv3まで順番に通知されるのでしょうか?

 

実行結果は次のようになります。

 

実行結果

 


click:div3

 

先ほどと同じです。

 

つまり、今回の例ではuseCaptureに何を指定しても、プログラムの動作に影響がありません。

 

またaddEventListenerは、登録した要素のみにイベントを通知することも重要なので、覚えておいてください。

useCaptureの意味があるケース

 

ではaddEventListenerの第三引数useCaptureは、どんなときに使い分ければいいのでしょうか。

 

それは階層が重なり合う要素に対して、各々同じイベントを登録するときです。

 

先ほどのhtmlはそのままで、JavaScriptを変更してみます。

 

重なり合う要素に同じイベントを登録

 

window.addEventListener( "DOMContentLoaded" , ()=> {

    const click = e =>{
        console.log( `click:currentTarget[${ e.currentTarget.id}] target[${ e.target.id}] phase[${ e.eventPhase}]` );
    }
    document.getElementById("div3").addEventListener("click",click);
    document.getElementById("div2").addEventListener("click",click);
});

 

div3の下にあるdiv2にも、クリックイベントを登録しました。

 

ブラウザで表示して、div3をクリックすると次のような結果となります。

 

実行結果

 


click:currentTarget[div3] target[div3] phase[2]
click:currentTarget[div2] target[div3] phase[3]

 

useCaptureはfalseなので、クリックされたdiv3からdiv2へとイベントが伝播しています。

 

ちなみにcurrentTargetは今処理している要素を指し、targetはクリックされた要素です。
関連記事:【JavaScript】 targetとcurrentTargetの覚書

 

またphaseは、1がキャプチャフェーズ、3がバブリングフェーズです。
ここまで触れていませんでしたが、ターゲットとなる要素が見つかるとキャプチャフェーズからターゲットフェーズに切り替わります。
phase[2]は、ターゲットフェーズです。

 

では、addEventListenerの第三引数useCaptureをtrueにしてみます。

 

addEventListenerの第三引数useCaptureをtrueに変更

 


    document.getElementById("div3").addEventListener("click",click,true);
    document.getElementById("div2").addEventListener("click",click,true);

 

ブラウザで表示して、div3をクリックすると次のような結果となります。

 

実行結果

 


click:currentTarget[div2] target[div3] phase[1]
click:currentTarget[div3] target[div3] phase[2]

 

キャプチャフェーズでのイベント通知が有効になったので、下層のdiv2からdiv3の順番でイベントが発生しています。

 

今回の例ではdiv2とdiv3が完全に重なり合っているためdiv3のみクリックできます。
しかし大きさを変えdiv2もクリックできるようにして、それぞれ異なる処理をおこなうケースの方が多いはずです。

 

その際に、どのような順番でイベントを発生させるのかを、自分で適切に制御する必要があります。

useCaptureの使い分け

 

ここまでuseCaptureが、キャプチャフェーズとバブリングフェーズを制御しているととれる書き方をしてきました。

 

しかし実際には、DOMをたどってイベント要素を探す作業(キャプチャフェーズ)中にイベントを通知するか、要素にたどり着いた後ルートへと遡る作業(バブリングフェーズ)中にイベントを通知するかのフラグでしかありません。

 

これまでの例で、イベント登録を次のようにおこなってみます。

 

useCaptureの使い分け

 


    document.getElementById("div3").addEventListener("click",click);
    document.getElementById("div2").addEventListener("click",click,true);
    document.getElementById("div1").addEventListener("click",click);

 

div1、div2、div3にそれぞれクリックイベントを登録して、div2のみuseCaptureにtrueを指定しています。

 

ブラウザ表示後、div3をクリックすると次のような結果になります。

 

実行結果

 


click:currentTarget[div2] target[div3] phase[1]
click:currentTarget[div3] target[div3] phase[2]
click:currentTarget[div1] target[div3] phase[3]

 

div2→div3→div1の順番でイベントが発生しました。

 

これは次の図のような流れの中で、各要素でuseCaptureを見てイベント通知を判断しているからです。

 

キャプチャフェーズ バブリングフェーズ useCapture

 

なお同じ要素に対して、trueとfalse両方のイベントを登録することも可能です。

 

trueとfalse両方のイベントを登録

 


document.getElementById("div3").addEventListener("click",click);
document.getElementById("div2").addEventListener("click",click);
document.getElementById("div2").addEventListener("click",click,true);
document.getElementById("div1").addEventListener("click",click);

 

実行結果

 


click:currentTarget[div2] target[div3] phase[1]
click:currentTarget[div3] target[div3] phase[2]
click:currentTarget[div2] target[div3] phase[3]
click:currentTarget[div1] target[div3] phase[3]

例題

 

次のDOM要素に対してクリックイベントを、div2→div4→div5→div5→div3→div2→div1の順で発生させます。

 

html

 

<div id="div1"  >
    <div id="div2"  >
        <div id="div3"  >
            <div id="div4"  >
                <div id="div5"  style="width:100px;height:100px;border:1px solid #555">

                </div>
            </div>
        </div>
    </div>
</div>

 

回答

 

まず発生させたい順番にaddEventListenerを記述します。

 

順番にaddEventListenerを記述

 


div2.addEventListener("click",click);
div4.addEventListener("click",click);
div5.addEventListener("click",click);
div5.addEventListener("click",click);
div3.addEventListener("click",click);
div2.addEventListener("click",click);
div1.addEventListener("click",click);

 

あらかじめdiv1などにはgetElementById()などで要素をセットしてあるとします。

 

クリックされる要素、ここではdiv5以前のaddEventListener()の第三引数useCaptureにtrueを指定します。

 

第三引数useCaptureにtrueを指定

 


div2.addEventListener("click",click,true);
div4.addEventListener("click",click,true);
div5.addEventListener("click",click,true);
div5.addEventListener("click",click);
div3.addEventListener("click",click);
div2.addEventListener("click",click);
div1.addEventListener("click",click);

 

div5はイベントが二つ登録されているので、どちらかをtrueにする必要があります。

 

この状態でdiv5をクリックすると、順番にイベントが発生します。

useCaptureをオブジェクトで指定

 

addEventListenerの第三引数は真偽値の他に、複数のオプションをオブジェクトを受け付けます。

 

オブジェクトは、次の決まった名称のプロパティをセットします。

 

options:{
 capture : 真偽値
 once : 真偽値
 passive : 真偽値
}

 

■options.capture

 

useCaptureで指定した真偽値と同じ意味を持ちます。

 

 

■options.once

 

イベント発生後に、イベントリスナーが削除されます。
つまり、一回のみイベントが発生します。

 

■options.passive

 

イベントの規定の動作(リンク遷移やチェックボックスの切り替え等)をキャンセルする、preventDefault()が使用可能かどうかを指定します。
この値がtrueの場合、イベント内でpreventDefault()を呼び出しても無効です。

 

基本的にはこのオプションのデフォルトはfalseですが、スマホのタッチイベントなど一部のイベントはtrueになるようです。
preventDefault()が効かないときは、このオプションをfalseにセットしてみてください。

removeEventListener() とuseCapture

 

addEventListener()は同一の要素に対して、useCaptureが異なれば同じ関数を登録することができます。

 

そのため、addEventListener() で登録したイベントをremoveEventListener()で削除するときは、useCaptureを一致させる必要があります。

 

 

removeEventListener() はuseCaptureを一致させる

 


document.getElementById("div2").addEventListener("click",click,true);

  // useCapture=trueで登録されているため、削除できない
document.getElementById("div2").removeEventListener("click",click);

 // 削除可能
document.getElementById("div2").removeEventListener("click",click,true);

 

オプションはcaptureプロパティのみチェックされます。

addEventListener()は関係ないイベントも伝播する

 

例えば次のようなラジオボタンがあるとします。

 

html

 

<div id="testarea">
    <label><input type="radio" name="test" value="1">Value:1</label>
    <label><input type="radio" name="test" value="2">Value:2</label>
    <label><input type="radio" name="test" value="3">Value:3</label>
</div>

 

このときdiv要素にchangeイベントを登録します。

 

JavaScript

 


document.getElementById("testarea").addEventListener("change",e =>{
            if( e.target.type !== "radio" ) return;
            console.log( `click:name[${e.target.name}] value[${e.target.value}]` );
        } );

 

div要素にはchangeイベントはありません。
しかしラジオボタンで発生したchangeイベントがフェーズで伝播して、div要素に通知されます。

 

このとき後から動的に選択肢を追加したとしても、div要素でイベントを捕捉できます。

 

■ラジオボタンにchangeイベントを登録した場合

 

  // 全ての選択肢にイベントを登録する
document.getElementsByName("test")
      .forEach( e => e.addEventListener("change",e =>func() ) );

 

動的に選択肢を追加したら、その選択肢にchangeイベントを登録する必要があります。

 

記事の内容について

 

こんにちはけーちゃんです。
説明するのって難しいですね。


「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。

そんなときは、ご意見もらえたら嬉しいです。

ご意見はこちら。
https://affi-sapo-sv.com/info.php