pointerenterはバブリングしないらしい

# はじめに

タイトルにあるとおり、DOMで発生するイベントの1つである `pointerenter` イベントはバブリングしないというお話です。

去年のアドベントカレンダーで書いたDOMイベントの伝搬に関する記事の延長です。バブリングってなに？という方はまずこちらを読んでいただければざっくり理解できるかと思います。

https://qiita.com/www-tacos/items/92ece4e161d8107665a0

# 「バブリングしない」とは？

[Mozillaのドキュメント](https://developer.mozilla.org/ja/docs/Web/API/PointerEvent#pointerenter)より、Pointer Event の pointerenter の説明に

> このイベントは、ポインティングデバイスが要素またはその子孫の 1 つのヒットテスト境界内に移動したときに発生します。 これには、ホバーに対応していない機器からの pointerdown イベントの結果も含まれます（pointerdown を参照）。 この種類のイベントは pointerover に似ていますが、バブリングしないという点が異なります。

とあります。

ここでいう「バブリングしない」というのがどういうことか、後述の章でいろいろ考察していますが、先に結論を書くと

**子要素から親要素への伝搬がなく、また親要素のイベント伝搬から順に実行される**

ということのようです。

・・・とだけいってもよくわからないと思うので、以降の章でパブリングの有無がイベント伝搬にどう影響するのか調べた内容を紹介します。

# バブリングする/しないイベント伝搬の動き

百聞は一見に如かず。ということでサンプルを用意しました。

## pointerover

まずはじめに、バブリングするイベントである `pointerover` のサンプルです。

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="eYXqVqW" data-pen-title="check-pointerenter" data-user="www-tacos" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/www-tacos/pen/eYXqVqW">
  check-pointerenter</a> by www-tacos (<a href="https://codepen.io/www-tacos">@www-tacos</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

赤色が親で青色が子になっていて、親と子それぞれにCapturingとBubblingそれぞれで発火するイベントリスナーを設定しています。

青色の四角形にマウスカーソルを重ねる（スマホならタッチする）と
**親のCapturing → 子のCapturing → 子のBubbling → 親のBubbling**
の順にイベントが検知されると思います。

こちらはキャプチャリング・バブリングの想定通りの動きですね。

## pointerenter①

つづいて、以下は `pointerenter` のサンプル1つめです。

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="ZYzWggj" data-pen-title="pointerenter-has-no-bubbling-1" data-user="www-tacos" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/www-tacos/pen/ZYzWggj">
  pointerenter-has-no-bubbling-1</a> by www-tacos (<a href="https://codepen.io/www-tacos">@www-tacos</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

ここではまず親と子の両方に対して `addEventLister` の第3引数を指定せずにイベントリスナーを設定しました。

さきほどと同様に青色の四角形にマウスカーソルを重ねる（スマホならタッチする）と
**親のBubbling → 子のBubbling**
の順にイベントが検知されると思います。

つまり第3引数の指定なしで設定したイベントリスナーがキャプチャリングフェーズで発火するのと同じように動いていることがわかります。

といったところで、まずこの結果からバブリングしないイベントは内部的にキャプチャリングフェーズに置き換えているのかな？と予想しました。まあ最終的にこの予想は外れるのですが...

## pointerenter②

さいごに、以下は `pointerenter` のサンプル2つめです。

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="azoNeBG" data-pen-title="pointerenter-has-no-bubbling" data-user="www-tacos" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/www-tacos/pen/azoNeBG">
  pointerenter-has-no-bubbling</a> by www-tacos (<a href="https://codepen.io/www-tacos">@www-tacos</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

ここでは `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だった。

上記の結果から、おそらく以下の図のように親のイベント発火→子のイベント発火の順に実行しているのだと思います。

![図.バブリングしないイベントの発火順序](https://www-tacos.github.io/qiita-images/20241209_pointerenter_bubbling/001.png)

こういうロジックだからこそ `addEventListener` の第3引数で明示的にキャプチャリングフェーズを指定してなくても親のイベントが先に実行され、結果的にキャプチャリングフェーズで実行しているように見えるのだと思われます。

したがって、最初にも書いたように、バブリングしないというのは

**子要素から親要素への伝搬がなく、また親要素のイベント伝搬から順に実行される**

ということなのだと思います。

ちなみにこれらはあくまで結果からの推測です。もし間違っていたらごめんなさい。

# バブリングしないイベント

`pointerenter` 以外にもバブリングしないイベントがいくつかあるようです。

* `pointerleave` (類似イベントの `pointerout` はバブリングする)
* `mouseenter`
* `mouseleave`
* `focus`
* `blur`
* `scroll`
* `resize`

バブリングしない理由をChatGPTに聞いたところ「用途が特定の要素に限定されるため、親要素への伝播が不要」だからだそうです。

たしかにfocusなんかは子要素から親要素に伝搬してしまうと最終的に親要素にフォーカスしたようになってしまうので伝搬は不要そうですね。

ただ親要素のイベントリスナーが無効になるわけではないので、親子要素にこれらのイベントリスナーを設定している場合は以前考慮が必要になりそうです。

# おわりに

いかがでしかた？
当記事が（存在するかわからないですが）バブリングに悩む方の助けになれば幸いです。

最後まで読んでいただきありがとうございました。