WebサイトへJavaScriptを追加していると、一つのボタンをクリックしただけなのに、別の処理まで実行されることがあります。
たとえば、モーダル内のボタンを押したらモーダル自体も閉じる、カード内のリンクを押したらカード全体のクリック処理まで動く、といった現象です。
イベントを設定した場所が間違っているように見えますが、原因は「イベント伝播」というブラウザの仕組みかもしれません。
今回は、クリックイベントが親要素へ伝わる仕組みと、意図しない動作を防ぐための考え方を初心者向けに解説します。
イベントとは
イベントとは、Webページ上で発生した操作や変化のことです。
JavaScriptでは、さまざまなイベントをきっかけに処理を実行できます。
代表的なイベントには、次のようなものがあります。
要素をクリックした
入力欄の内容を変更した
キーボードを押した
フォームを送信した
ページの読み込みが完了した
マウスを要素へ重ねた
画面をスクロールした
たとえば、メニューボタンのクリックをきっかけにナビゲーションを表示する処理も、イベントを利用した実装です。
イベントは一つの要素だけで完結するとは限りません。HTMLの親子関係に沿って、周囲の要素へ伝わる場合があります。
HTMLには親子関係がある
HTMLでは、要素の中に別の要素を入れて構造を作ります。
たとえば、一つのカードの中に画像、見出し、説明文、リンクがある場合、カードが親要素で、その中にある各項目が子要素です。
モーダルであれば、画面全体を覆う背景の中に、内容を表示する枠や閉じるボタンがあります。
このような親子関係は、レイアウトだけでなく、JavaScriptのイベントにも影響します。
子要素で発生したクリックは、その子要素だけで終わらず、親要素にも伝わっていくことがあります。
バブリングとは
子要素で発生したイベントが、親要素へ順番に伝わっていく仕組みをバブリングと呼びます。
水中の泡が下から上へ浮かんでいくように、クリックされた要素から外側の要素へイベントが伝わるイメージです。
カード全体と、その中にあるボタンの両方へクリック処理が設定されている場合、ボタンを押すと次のような順番で処理されることがあります。
ボタンのクリック処理が実行される
イベントがカードへ伝わる
カードのクリック処理も実行される
さらに外側の親要素へイベントが伝わる
この動作はJavaScriptの不具合ではなく、ブラウザに用意されている標準的な仕組みです。
モーダルで起こりやすい問題
イベント伝播による問題が起こりやすい例の一つが、モーダルです。
モーダルの背景をクリックしたときに閉じる処理を設定すると、利用者は枠の外側を押して画面を閉じられます。
しかし、モーダル内のボタンや入力欄をクリックしたときも、そのイベントが背景まで伝わることがあります。
背景に設定された処理が実行されると、モーダル内を操作しただけなのに画面が閉じてしまいます。
この場合は、「背景部分そのものがクリックされたのか」「その内側の要素がクリックされたのか」を区別する必要があります。
背景へ届いたすべてのクリックで閉じるのではなく、実際にクリックされた場所を確認してから処理を実行します。
カード全体をクリックできる実装でも注意が必要
制作実務では、商品カードや記事カードのどこを押しても詳細ページへ移動できるデザインがあります。
カード全体へクリック処理を設定すれば実装できますが、カード内に別のリンクやボタンがある場合は注意が必要です。
たとえば、カード全体は詳細ページへ移動し、内部のボタンはお気に入り登録を行う設計を考えてみます。
お気に入りボタンを押したとき、そのクリックがカードまで伝わると、登録処理の直後に詳細ページへ移動する可能性があります。
利用者から見ると、ボタンを押したのに勝手に別のページへ移動したように感じます。
クリックできる要素を入れ子にしすぎず、カード内の各操作が競合しない構造を考えることが大切です。
イベントを途中で止める方法がある
JavaScriptには、イベントが親要素へ伝わるのを途中で止める仕組みがあります。
子要素の処理だけを実行し、親要素のクリック処理を動かしたくない場合に利用できます。
モーダル内の操作や、カード内の独立したボタンなどでは有効な方法です。
ただし、意図しない動作が起きるたびにイベント伝播を止めればよいわけではありません。
イベントを止めると、その外側でイベントを利用している別の機能まで動かなくなる可能性があります。
たとえば、ページ全体でクリックを監視してメニューを閉じる処理や、操作状況を計測する処理がある場合、途中でイベントを止めることで影響が出ることがあります。
まずHTMLの構造や処理の役割を整理し、本当に伝播を止める必要がある場所だけで使用します。
イベント伝播は便利な場面もある
イベント伝播は、意図しない処理を起こすだけの仕組みではありません。
親要素へ一つのイベントを設定し、その中にある複数の子要素をまとめて管理する方法にも利用できます。
この考え方をイベント委譲と呼びます。
たとえば、商品一覧に多数のボタンがある場合、すべてのボタンへ個別にイベントを設定する代わりに、一覧全体を囲む親要素でクリックを受け取れます。
親要素へ届いたイベントを調べ、どのボタンが押されたのかに応じて処理を分けます。
子要素から親要素へイベントが伝わる仕組みを、あえて利用しているわけです。
後から追加される要素にも対応しやすい
イベント委譲は、JavaScriptによって後から追加される要素を扱う場合にも便利です。
ページを開いた時点では存在しないボタンへ、その時点で直接イベントを設定することはできません。
検索結果の追加読み込み、商品一覧の絞り込み、チャットメッセージの追加などでは、操作後に新しい要素が作られます。
親要素へイベントを設定しておけば、後から追加された子要素のクリックも親へ伝わります。
そのため、要素を追加するたびにイベントを設定し直さなくても処理できる場合があります。
ただし、親要素に届いたすべてのクリックへ反応すると、関係のない場所でも処理が動いてしまいます。
クリックされた要素が目的のボタンなのかを正しく判定することが重要です。
クリックされた要素と処理を設定した要素は違うことがある
イベントを調べるときは、「実際にクリックされた要素」と「イベント処理が設定されている要素」を分けて考えます。
たとえば、ボタンの中にアイコンや文字がある場合、利用者がクリックした場所によって、直接クリックされた要素はアイコンや文字になることがあります。
一方、クリック処理はボタンや、その外側の一覧へ設定されているかもしれません。
見た目では同じボタンを押していても、JavaScriptから見た直接の対象は異なる場合があります。
目的の要素だけを完全一致で判定すると、ボタンの背景を押したときは動くのに、アイコンを押したときは動かないという問題が起こることがあります。
クリックされた場所から、目的のボタンやリンクを適切に探す考え方が必要です。
二重実行の原因がイベント伝播とは限らない
クリック処理が二回実行されたからといって、必ずイベント伝播が原因とは限りません。
次のような原因でも同じような現象が起こります。
同じ要素へイベントを二回設定している
JavaScriptファイルを重複して読み込んでいる
テーマとプラグインの両方が同じ処理を行っている
画面を更新するたびにイベントを追加している
クリックとフォーム送信の両方で同じ処理を実行している
ボタンの標準動作とJavaScriptの処理が重なっている
パソコン用とスマートフォン用の要素が同時に存在している
まず、親要素にもクリック処理があるかを確認します。
親に処理がなければ、イベントの重複登録やファイルの二重読み込みなど、別の原因を調べます。
むやみに標準動作まで止めない
イベント伝播を止めることと、リンク移動やフォーム送信などの標準動作を止めることは別です。
リンクを押したときのページ移動や、送信ボタンを押したときのフォーム送信は、ブラウザが最初から持っている動作です。
問題の原因を理解しないまま標準動作を止めると、リンクが開かない、フォームが送れないといった別の不具合が発生します。
必要なのが親要素への伝播を止めることなのか、標準動作を止めることなのかを区別する必要があります。
フォームの入力内容を確認してから送信する場合など、標準動作を制御する明確な理由があるときだけ使用します。
HTMLの構造から見直した方がよい場合もある
JavaScriptだけで動作を調整するより、HTMLの構造を見直した方が分かりやすくなることがあります。
特に、リンクの中へ別のボタンを置いたり、クリックできる要素を何重にも重ねたりすると、操作の役割が曖昧になります。
次の点を確認してみましょう。
ページ移動とその場の操作が混在していないか
一つの範囲に複数のクリック処理が重なっていないか
親要素全体をクリック可能にする必要があるか
ボタンとリンクを目的に応じて使い分けているか
見た目だけのために不自然な入れ子を作っていないか
キーボードでも同じ操作ができるか
HTMLの役割が整理されていれば、JavaScriptで例外的な処理を増やさずに済みます。
問題を切り分けるときの確認順序
クリックしたときに意図しない処理が動く場合は、次の順番で確認すると原因を探しやすくなります。
どの要素をクリックしたときに発生するか
本来どの処理だけを動かしたいのか
クリックした要素の親にも処理が設定されていないか
さらに外側の要素でクリックを監視していないか
同じイベントが複数回登録されていないか
JavaScriptファイルを重複して読み込んでいないか
ボタンやリンクの標準動作が関係していないか
イベントを止めた場合、別の機能へ影響しないか
HTMLの構造を簡単にできないか
マウスだけでなくキーボードでも操作できるか
一時的に各処理が実行された場所を記録すると、どの順番でイベントが動いているのかを確認しやすくなります。
まとめ
JavaScriptのイベント伝播とは、クリックなどのイベントがHTMLの親子関係に沿って別の要素へ伝わる仕組みです。
子要素で発生したイベントが親要素へ伝わる動きを、バブリングと呼びます。
この仕組みによって、モーダル内をクリックしたのに画面が閉じたり、カード内のボタンを押したのにページ移動まで起きたりすることがあります。
必要な場所ではイベントの伝播を止められますが、理由を確認せず多用すると、別の機能が動かなくなる可能性があります。
一方、イベント伝播を利用すれば、複数の子要素を親要素でまとめて管理でき、後から追加された要素にも対応しやすくなります。
クリックした要素だけを見るのではなく、その親や外側にどのような処理が設定されているかまで確認する。
この視点を持つことで、JavaScriptの意図しない動作を切り分けやすくなり、追加や修正にも強い実装を作れるようになります。