はじめに
タイトルにあるとおり、DOMで発生するイベントの1つである pointerenter
イベントはバブリングしないというお話です。
去年のアドベントカレンダーで書いたDOMイベントの伝搬に関する記事の延長です。バブリングってなに?という方はまずこちらを読んでいただければざっくり理解できるかと思います。
「バブリングしない」とは?
Mozillaのドキュメントより、Pointer Event の pointerenter の説明に
このイベントは、ポインティングデバイスが要素またはその子孫の 1 つのヒットテスト境界内に移動したときに発生します。 これには、ホバーに対応していない機器からの pointerdown イベントの結果も含まれます(pointerdown を参照)。 この種類のイベントは pointerover に似ていますが、バブリングしないという点が異なります。
とあります。
ここでいう「バブリングしない」というのがどういうことか、後述の章でいろいろ考察していますが、先に結論を書くと
子要素から親要素への伝搬がなく、また親要素のイベント伝搬から順に実行される
ということのようです。
・・・とだけいってもよくわからないと思うので、以降の章でパブリングの有無がイベント伝搬にどう影響するのか調べた内容を紹介します。
バブリングする/しないイベント伝搬の動き
百聞は一見に如かず。ということでサンプルを用意しました。
pointerover
まずはじめに、バブリングするイベントである pointerover
のサンプルです。
See the Pen check-pointerenter by www-tacos (@www-tacos) on CodePen.
赤色が親で青色が子になっていて、親と子それぞれにCapturingとBubblingそれぞれで発火するイベントリスナーを設定しています。
青色の四角形にマウスカーソルを重ねる(スマホならタッチする)と
親のCapturing → 子のCapturing → 子のBubbling → 親のBubbling
の順にイベントが検知されると思います。
こちらはキャプチャリング・バブリングの想定通りの動きですね。
pointerenter①
つづいて、以下は pointerenter
のサンプル1つめです。
See the Pen pointerenter-has-no-bubbling-1 by www-tacos (@www-tacos) on CodePen.
ここではまず親と子の両方に対して addEventLister
の第3引数を指定せずにイベントリスナーを設定しました。
さきほどと同様に青色の四角形にマウスカーソルを重ねる(スマホならタッチする)と
親のBubbling → 子のBubbling
の順にイベントが検知されると思います。
つまり第3引数の指定なしで設定したイベントリスナーがキャプチャリングフェーズで発火するのと同じように動いていることがわかります。
といったところで、まずこの結果からバブリングしないイベントは内部的にキャプチャリングフェーズに置き換えているのかな?と予想しました。まあ最終的にこの予想は外れるのですが...
pointerenter②
さいごに、以下は pointerenter
のサンプル2つめです。
See the Pen pointerenter-has-no-bubbling by www-tacos (@www-tacos) on CodePen.
ここでは addEventLister
の第3引数を指定してのイベントリスナー設定も追加で行っています。
さきほどと同様に青色の四角形にマウスカーソルを重ねる(スマホならタッチする)と
親のCapturing → 親のBubbling → 親のCapturing → 子のCapturing → 子のBubbling
の順にイベントが検知されると思います。
:
:
ん?親のCapturingが2回もでている?どういうことなんだ...
「バブリングしない」の実態
よくわからないのでいくつか追加で調べてみました。
-
2つめのサンプルでは
event.target
は以下のようになっていた。- 1.親のCapturing (target=div#parent)
- 2.親のBubbling (target=div#parent)
- 3.親のCapturing (target=div#child)
- 4.子のCapturing (target=div#child)
- 5.子のBubbling (target=div#child)
-
pointeroverの時は
event.target
はすべてdiv#childだった。
上記の結果から、おそらく以下の図のように親のイベント発火→子のイベント発火の順に実行しているのだと思います。
こういうロジックだからこそ addEventListener
の第3引数で明示的にキャプチャリングフェーズを指定してなくても親のイベントが先に実行され、結果的にキャプチャリングフェーズで実行しているように見えるのだと思われます。
したがって、最初にも書いたように、バブリングしないというのは
子要素から親要素への伝搬がなく、また親要素のイベント伝搬から順に実行される
ということなのだと思います。
ちなみにこれらはあくまで結果からの推測です。もし間違っていたらごめんなさい。
バブリングしないイベント
pointerenter
以外にもバブリングしないイベントがいくつかあるようです。
-
pointerleave
(類似イベントのpointerout
はバブリングする) mouseenter
mouseleave
focus
blur
scroll
resize
バブリングしない理由をChatGPTに聞いたところ「用途が特定の要素に限定されるため、親要素への伝播が不要」だからだそうです。
たしかにfocusなんかは子要素から親要素に伝搬してしまうと最終的に親要素にフォーカスしたようになってしまうので伝搬は不要そうですね。
ただ親要素のイベントリスナーが無効になるわけではないので、親子要素にこれらのイベントリスナーを設定している場合は以前考慮が必要になりそうです。
おわりに
いかがでしかた?
当記事が(存在するかわからないですが)バブリングに悩む方の助けになれば幸いです。
最後まで読んでいただきありがとうございました。