イベントバブリングとキャプチャの違いは何ですか?いつバブリングとキャプチャを使用する必要がありますか?
8 に答える
イベントのバブリングとキャプチャは、HTML DOM APIでのイベント伝播の2つの方法です。イベントが別の要素内の要素で発生し、両方の要素がそのイベントのハンドルを登録している場合です。イベント伝播モードは、要素がイベントを受信する順序を決定します。
バブリングでは、イベントは最初に最も内側の要素によってキャプチャおよび処理され、次に外側の要素に伝播されます。
キャプチャを使用すると、イベントは最初に最も外側の要素によってキャプチャされ、内側の要素に伝播されます。
キャプチャは「トリックリング」とも呼ばれ、伝播順序を覚えておくのに役立ちます。
トリクルダウン、バブルアップ
昔、Netscapeはイベントのキャプチャを提唱し、Microsoftはイベントのバブリングを推進していました。どちらもW3Cドキュメントオブジェクトモデルイベント標準(2000)の一部です。
IE <9はイベントバブリングのみを使用しますが、IE9+とすべての主要なブラウザーは両方をサポートします。一方、複雑なDOMの場合、イベントバブリングのパフォーマンスはわずかに低下する可能性があります。
を使用してaddEventListener(type, listener, useCapture)
、バブリング(デフォルト)モードまたはキャプチャモードのいずれかでイベントハンドラを登録できます。キャプチャモデルを使用するには、3番目の引数をとして渡しますtrue
。
例
<div>
<ul>
<li></li>
</ul>
</div>
上記の構造では、要素でクリックイベントが発生したと想定していますli
。
モデルのキャプチャでは、イベントはdiv
最初に処理され(のクリックイベントハンドラーがdiv
最初に起動します)、次に、で処理ul
され、最後にターゲット要素で処理されli
ます。
バブリングモデルでは、逆のことが起こります。イベントは、最初に、によって処理されli
、次に、によって処理されul
、最後にdiv
要素によって処理されます。
詳細については、を参照してください。
- QuirksModeでのイベントの順序
- MDNのaddEventListener
- QuirksModeで高度なイベント
以下の例では、強調表示された要素のいずれかをクリックすると、イベント伝播フローのキャプチャフェーズが最初に発生し、次にバブリングフェーズが発生することがわかります。
var logElement = document.getElementById('log');
function log(msg) {
logElement.innerHTML += ('<p>' + msg + '</p>');
}
function capture() {
log('capture: ' + this.firstChild.nodeValue.trim());
}
function bubble() {
log('bubble: ' + this.firstChild.nodeValue.trim());
}
function clearOutput() {
logElement.innerHTML = "";
}
var divs = document.getElementsByTagName('div');
for (var i = 0; i < divs.length; i++) {
divs[i].addEventListener('click', capture, true);
divs[i].addEventListener('click', bubble, false);
}
var clearButton = document.getElementById('clear');
clearButton.addEventListener('click', clearOutput);
p {
line-height: 0;
}
div {
display:inline-block;
padding: 5px;
background: #fff;
border: 1px solid #aaa;
cursor: pointer;
}
div:hover {
border: 1px solid #faa;
background: #fdd;
}
<div>1
<div>2
<div>3
<div>4
<div>5</div>
</div>
</div>
</div>
</div>
<button id="clear">clear output</button>
<section id="log"></section>
説明:
quirksmode.orgには、これについての優れた説明があります。一言で言えば(quirksmodeからコピー):
イベントキャプチャ
イベントキャプチャを使用する場合
| | ---------------| |----------------- | element1 | | | | -----------| |----------- | | |element2 \ / | | | ------------------------- | | Event CAPTURING | -----------------------------------
element1のイベントハンドラーが最初に起動し、element2のイベントハンドラーが最後に起動します。
イベントバブリング
イベントバブリングを使用する場合
/ \ ---------------| |----------------- | element1 | | | | -----------| |----------- | | |element2 | | | | | ------------------------- | | Event BUBBLING | -----------------------------------
element2のイベントハンドラーが最初に起動し、element1のイベントハンドラーが最後に起動します。
何を使う?
それはあなたが何をしたいかによります。これ以上のことはありません。違いは、イベントハンドラーの実行順序です。ほとんどの場合、バブリングフェーズでイベントハンドラーを起動することは問題ありませんが、早期に起動する必要がある場合もあります。
要素1と要素2の2つの要素がある場合、要素2は要素1の内部にあり、両方の要素を使用してイベントハンドラーをアタッチします。たとえば、onClickとします。要素2をクリックすると、両方の要素のeventHandlerが実行されます。ここで問題は、イベントが実行される順序です。要素1に関連付けられたイベントが最初に実行される場合、それはイベントキャプチャと呼ばれ、要素2に関連付けられたイベントが最初に実行される場合、これはイベントバブリングと呼ばれます。W3Cによると、イベントは、ターゲットに到達するまでキャプチャフェーズで開始され、要素に戻ってからバブリングを開始します。
キャプチャとバブリングの状態は、addEventListenerメソッドのuseCaptureパラメーターによって認識されます。
eventTarget.addEventListener(type、listener、[、useCapture]);
デフォルトでは、useCaptureはfalseです。それはバブリング段階にあることを意味します。
var div1 = document.querySelector("#div1");
var div2 = document.querySelector("#div2");
div1.addEventListener("click", function (event) {
alert("you clicked on div 1");
}, true);
div2.addEventListener("click", function (event) {
alert("you clicked on div 2");
}, false);
#div1{
background-color:red;
padding: 24px;
}
#div2{
background-color:green;
}
<div id="div1">
div 1
<div id="div2">
div 2
</div>
</div>
trueとfalseを変更してみてください。
javascript.infoにあるこのチュートリアルは、このトピックを説明する上で非常に明確であることがわかりました。そして最後の3つのポイントの要約は本当に重要なポイントに話しかけています。私はここでそれを引用します:
- イベントは最初に最も深いターゲットまでキャプチャされ、次にバブルアップします。IE <9では、バブルするだけです。
addEventListener
最後の引数を除いて、すべてのハンドラーはバブリングステージで動作 しますtrue
。これは、キャプチャステージでイベントをキャッチする唯一の方法です。- バブリング/キャプチャは、
event.cancelBubble=true
(IE)またはevent.stopPropagation()
他のブラウザで停止できます。
Event.eventPhase
イベントがターゲットにあるのか、それとも他の場所から来ているのかを知ることができるプロパティもあり、ブラウザによって完全にサポートされています。
受け入れられた回答からのすでに素晴らしいスニペットeventPhase
を拡張すると、これはプロパティを使用した出力です
var logElement = document.getElementById('log');
function log(msg) {
if (logElement.innerHTML == "<p>No logs</p>")
logElement.innerHTML = "";
logElement.innerHTML += ('<p>' + msg + '</p>');
}
function humanizeEvent(eventPhase){
switch(eventPhase){
case 1: //Event.CAPTURING_PHASE
return "Event is being propagated through the target's ancestor objects";
case 2: //Event.AT_TARGET
return "The event has arrived at the event's target";
case 3: //Event.BUBBLING_PHASE
return "The event is propagating back up through the target's ancestors in reverse order";
}
}
function capture(e) {
log('capture: ' + this.firstChild.nodeValue.trim() + "; " +
humanizeEvent(e.eventPhase));
}
function bubble(e) {
log('bubble: ' + this.firstChild.nodeValue.trim() + "; " +
humanizeEvent(e.eventPhase));
}
var divs = document.getElementsByTagName('div');
for (var i = 0; i < divs.length; i++) {
divs[i].addEventListener('click', capture, true);
divs[i].addEventListener('click', bubble, false);
}
p {
line-height: 0;
}
div {
display:inline-block;
padding: 5px;
background: #fff;
border: 1px solid #aaa;
cursor: pointer;
}
div:hover {
border: 1px solid #faa;
background: #fdd;
}
<div>1
<div>2
<div>3
<div>4
<div>5</div>
</div>
</div>
</div>
</div>
<button onclick="document.getElementById('log').innerHTML = '<p>No logs</p>';">Clear logs</button>
<section id="log"></section>
バブリング
Event propagate to the upto root element is **BUBBLING**.
キャプチャ
Event propagate from body(root) element to eventTriggered Element is **CAPTURING**.
他の人が言ったように、バブリングとキャプチャは、いくつかのネストされた要素が特定のイベントを受け取る順序を記述します。
最も内側の要素は奇妙に見えるかもしれないことを指摘したいと思います。実際、この場合、イベントリスナーが追加される順序は重要です。
次の例では、キャプチャdiv2
はバブリングよりも最初に実行されます。バブリング中div4
はキャプチャよりも先に実行されます。
function addClickListener (msg, num, type) {
document.querySelector("#div" + num)
.addEventListener("click", () => alert(msg + num), type);
}
bubble = (num) => addClickListener("bubble ", num, false);
capture = (num) => addClickListener("capture ", num, true);
// first capture then bubble
capture(1);
capture(2);
bubble(2);
bubble(1);
// try reverse order
bubble(3);
bubble(4);
capture(4);
capture(3);
#div1, #div2, #div3, #div4 {
border: solid 1px;
padding: 3px;
margin: 3px;
}
<div id="div1">
div 1
<div id="div2">
div 2
</div>
</div>
<div id="div3">
div 3
<div id="div4">
div 4
</div>
</div>
編集:このような動作はブラウザによって異なる場合があります(たとえば、現在Firefoxで発生しますが、ChromeとEdgeでは発生しません)。それでも、それを知っておくべきだと思います。