JavaScriptのイベントバブリングとキャプチャリングについて知る機会があったので簡単にまとめます。
バブリングとは
ご存知の方も多いと思いますが、子要素で発火したイベントが親要素にまで伝播することをバブリングと言います。よく考えてみれば普段から目にする光景で、
<div id='button'>
<div>テキスト</div>
<div>テキスト2</div>
</div>
このようにid='button'のdiv要素にクリックイベント時の処理を追加した時、内部のテキスト部分(子要素のdiv)をクリックしても処理が実行されます。これは子要素から親へとイベントが伝播しているためです(ボタン内部の要素もボタンの一部なので、感覚的にこの挙動に違和感がなく、バブリングについて意識することがありませんね)
また、子 → 親 → その親 → その親 ... とどんどん上へバブリングしていくイベントですが、イベントリスナーに渡すコールバック関数のイベント引数のcurrentTargetでアクセスできます。逆に、イベントが生じた出発源となる要素にはtargetでアクセスできます。
キャプチャリングとは
①イベントが生じた要素 → ②その先祖要素、の順でバブリングするイメージですが、⓪番目にあたるフェーズがあります。それがキャプチャリングフェーズで、
⓪windowオブジェクトから出発してイベントが下りてくる → ①targetでイベントが生じる → ②親方向へイベントが上がっていく
こういう順番でイベントは降りて来て、そして去っていきます。
以下のコードではその順をコンソ-ルで確認できます。なお、キャプチャリングフェーズのイベントはaddEventListenerの第3引数にtrueを設定することで取得できます。
<body>
<div id="parent">
親
<div id="child">
子
<div id="grandchild">孫</div>
</div>
</div>
</body>
document.addEventListener("DOMContentLoaded", () => {
const parentNode = document.getElementById("parent");
const childNode = document.getElementById("child");
const grandchildNode = document.getElementById("grandchild");
if (!parentNode || !childNode || !grandchildNode) return;
[parentNode, childNode, grandchildNode].forEach((node) => {
node.addEventListener("click", (e) => {
console.log(e.currentTarget.id + "キャプチャリングなし");
});
node.addEventListener(
"click",
(e) => {
console.log(e.currentTarget.id + "キャプチャリング");
},
true //キャプチャリングフェーズでイベントを検知
);
});
});
孫をクリックすると、以下の出力となります。
parentキャプチャリング
childキャプチャリング
grandchildキャプチャリング
grandchildキャプチャリングなし
childキャプチャリングなし
parentキャプチャリングなし
降ってきて、そして先祖方向へ去っていくことがお分かりになるかと思います。