JavaScriptのイベントバブリングとは何か|子要素をクリックしたら、親要素のクリックイベントまで一緒に発火していた
クリックしたのは子要素だけなのに、なぜか親要素のイベントまで動いてしまう。
原因は、要素から要素へイベントが伝わる「バブリング」という仕組みだった。
モーダルを閉じるボタンを作っていたときだった。
背景をクリックしたらモーダルが閉じる仕様にしたくて、背景の要素にクリックイベントを仕込んだ。
ところが、モーダルの中の普通のボタンを押しただけなのに、背景のクリックイベントまで一緒に動いてしまった。
クリックしたのは子要素なのに、親要素まで反応していた
原因を調べて知ったのが「イベントバブリング」という仕組みだった。
ある要素でクリックなどのイベントが発生すると、そのイベントはまずクリックされた要素自身で処理され、そのあと親要素、さらにその親、というように外側へ向かって伝わっていく。
僕のモーダルで言えば、ボタン(子)をクリックしたイベントが、そのまま背景(親)にも伝わって、背景のクリックイベントまで発火していた、ということだった。
伝播を止める、たった1行
対処はシンプルで、ボタンのクリックイベントの中で event.stopPropagation() を呼ぶだけだった。
このメソッドを呼ぶと、イベントがそこから先の親要素へ伝わるのを止められる。
ボタンのクリック処理の先頭に1行足しただけで、背景のクリックイベントは発火しなくなった。
止めてよい場面と、止めるとまずい場面
ただ、何でもかんでも stopPropagation() で止めればいいわけではない、ということも分かってきた。
例えばサイト全体でクリック位置を計測しているような仕組みがある場合、途中でイベントを止めてしまうと、その計測まで届かなくなる。
自分が今触っている要素の都合だけで止めてしまうと、別の場所で動いているはずの処理が静かに動かなくなる、ということが起こり得る。
モーダルのケースでは、他に伝播を利用している処理が無いことを確認してから使った。
バブリングのおかげで動いている処理もある
逆に、バブリングの仕組みをそのまま利用して書かれているコードも多い。
一覧の行をクリックしたら詳細を開く、というような処理を、行ひとつひとつにイベントを付けるのではなく、一覧全体(親要素)に1つだけイベントを付けて、どの子要素がクリックされたかを event.target で調べて判定する、という書き方がある。
これはイベントデリゲーションと呼ばれていて、行の数があとから増えても親要素のイベントは1つのままでいい、という利点がある。
バブリングは厄介者というより、意図して使えば便利な仕組みだったのだと、ここでようやく腑に落ちた。
意図しない発火を見つけたら、まず伝わり方を疑う
ボタンを押しただけなのに別の場所が反応する、というバグに当たったら、まず子要素から親要素へイベントが伝わっていないかを疑うようにしている。
止めるべきか、むしろ利用すべきかは場面によって変わるので、仕組みを知らないまま触ると、どちらの判断もできないままになってしまう。
ほかにも書いています
受託・note・スタンプこういう仕組みを作る相談を受けています
毎日の更新・データ集め・集計の自動化/3万円〜収益がいくらだったか、どの施策が外れたか
note / 個人の視点で掘り下げていますスタンプラリーに挑戦する
行った場所は、現地チェックインで記録に残せます※受託の相談とnoteは、運営者が個人で受けている・書いているものです。
