DOMDOMタイムス#14: Eventインタフェースのbubblingとcomposed
めちゃくちゃ久しぶりの更新になってしまったドムドムタイムスです👶
(JSConfに全身全霊を捧げていました!)
さて、今日はEventインターフェースのbubblingとcomposedについてチラチラ見ていってみます。
イベントのインターフェースのおさらい
まずイベントがどういうインターフェースになっていたか軽く確認してみましょう!
例えばclickイベントはPointerEventインターフェースを持ったオブジェクトの1つでした。
このPointerEventインターフェースとやらは辿っていってみると、下のようにEventインターフェースをinheritしているわけですね。
Event <- UIEvent <- MouseEvent <- PointerEvent
Eventインターフェースを起点として、いろいろなイベントが実装されています。
例えばEventインターフェースをinheritしているUIEventと同じレベルにSubmitEventやCustomEventなどがあったり、マニアックなものだとGamepadEventなんかもあったり。もちろんもっとたくさんあります。
また、UIEventをinheritしているものとなると、その名の通りでUIに関わるイベントがてんこもりです🌞
MouseEventからKeyboardEvent、InputEvent、TouchEventなんかも。
気になる人はMDNのこのページからディグってみよう!
Eventインターフェースのbubblingとcomposed
Eventインターフェースが持つbubblingとcomposedなる2つのプロパティが今日のテーマです👀
これらプロパティはそれぞれ下記を定義していて、その値は各イベントごとに決められているわけヨネ。もちろんCustomEventなんかは自分で決められますが👶
-
bubbling: trueならバブリングする。falseならしない -
composed: trueならイベントはバブリング/キャプチャリングのときにshadow rootを超える。falseならshadow root止まり。
イベントの代表例をいくつかマッピングしてみると、以下のようになります。
| bubbling: true | bubbling: false | |
|---|---|---|
| composed: true | Aグループ(例: click(*1)) | Bグループ(例: focus, blur) |
| composed: false | Cグループ(例: mutationEvent(*2)) | Dグループ(例: mouseenter, mouseleave) |
*1: なんだかんだほとんどのUI Eventがここ
*2: documentが絡むもの(e.g.DOMNodeInsertedIntoDocument)は当てはまらない
で、ちょっと分かりやすくなるようにイベントの流れを図にしてみるとこんな感じ(ただしevent targetはshadow root外からだとshadow rootを持つ要素に書き換えられちゃうことに注意!!)。

こうだろ〜!と思ってると意外と違う、なんてことがDOMを触っていると(自分は)あります🐤
変な動きしてるなと思ったときは、このあたりを疑ってみるのもいいかもしれません。
意外だったイベント
自分が初めて見たとき、なんで!?ってなったものを2つ書いておいてみます。
scrollイベント
1つめはscrollイベントがバブリングしないという有名な話です🌞
自分は最初これを知らずにめちゃくちゃハマりました。
というか、そもそもscrollのbubblingやcomposedはどこに仕様が載っているのでしょうか……?分かる方いたら教えてください😭
ちなみにChrome(v119)でやってみたところ、target要素でだけはバブリングフェーズでも反応しているっぽいですね。Firefox(v119)ではそんなことなさそうですが……汗
Chrome(v119) : window(capturing) -> target(capturing) -> target(bubbling) -> おわり
Firefox(v119): window(capturing) -> target(capturing) -> おわり
一方で、少なくともChrome(v119)やFirefox(v119)においては、少なくともdocumentがターゲットなら、scrollイベントのbubblingプロパティはtrueです。バブリングします。
でも、windowオブジェクトのすぐ下なので「バブリングしていると言ってもねえ……」という感が自分にはありますが👶
とりあえず色々と「なんで!?」感あるので、ここにノミネートされています。とりあえず仕様を見つけたいところです。
inputイベントとchangeイベント
inputイベントとchangeイベントも若干クセがあります。
両者ともに同じようなイベントに見えますがcomposedの値が違うわけですね。inputがtrue、changeがfalseです。
ちなみにこれはwhatwgでも議論になっています。歴史的背景、という感じなので興味がある人は読んでみて下さい!
まとめ
イベントごとに異なるbubblingとcomposedはたまにハマりどころになる印象があります。
前にFloating UI(旧Popper)のデバッグをしているときかなんかに、それこそscrollのバブリングでハマった覚えがあります。
こりゃ大変ですわ……😭
Discussion