uhyohyo.net

JavaScript初級者から中級者になろう

三章第三回 イベントバブリング

このページの最終更新日:

今回は、イベントバブリングについて解説します。DOMのイベントシステムの中でも面白いところなので、ぜひマスターしてください。

さっそくですが、次のサンプルを見てみましょう。


<!doctype html>
<html>
  <head>
    <title>test</title>
  </head>
  <body onclick="console.log('body');" style="background-color:#eee;border:10px solid black">
    <div onclick="console.log('div');" style="background-color:yellow;width:300px;height:300px;padding:10px">
      <p onclick="console.log('p');" style="background-color:aqua">test</p>
    </div>
  </body>
</html>

body、div、pのそれぞれにonclick属性がついています。また、スタイルシートでいろいろ色分けしています。黄色い部分がdivで、水色の部分がpです。ついでに、bodyには枠もつけてみました。余談ですが、実際に見てみるとわかるように、bodyの領域というのは画面全体ではありません。body要素にイベントを設定する際に罠となることもありますから、注意してください。

上のHTMLのbody要素以下の木構造は次のようになっています。久しぶりに木構造を出すので改行のテキストノードなども省略していません。

body
  • #text (改行)
  • div
    • #text (改行)
    • p
      • #text "test"

さて、body要素をクリックすると'body'のログが、div要素なら'div'のログが、p要素なら'p'のログが出るように思います。しかし、やってみると、実際は少し違います。

body要素のときはいいのですが、div要素をクリックすると、「div」が出た後に「body」も出ます。さらに、p要素の場合、「p」「div」「body」と3つも出ます。

このことが意味するのは、ある要素でイベントが起こると、その親要素でもイベントが起こるということです。

具体的には、次のようになっています。

例えばp要素をクリックした場合、

body
  • #text (改行)
  • div
    • #text (改行)
    • p ←ここ
      • #text "test"

まず、ここでイベントが発生し、「p」が表示されます。なお、実際にクリックされたのはテキストですが、イベントが発生するのはそこの要素であるということになっています。テキストノードではイベントは発生しません。

その後、

body
  • #text (改行)
  • div ←ここ
    • #text (改行)
    • p
      • #text "test"

このようにその親にイベントが伝わります。ここで、p要素の親はdiv要素ですから、div要素でもイベントが発生し、「div」が表示されます。

さらに、

body ←ここ
  • #text (改行)
  • div
    • #text (改行)
    • p
      • #text "test"

このように、さらに親に伝わってbody要素でもイベントが発生し、「body」が表示されます。

今回は木構造をbody以下しか書いてませんが、実際にはbodyの上にはさらにhtml要素があるので、html要素でもイベントは発生しています。ただ、html要素にはイベント属性がないため、何も起こりません。

このように、イベントが親へ親へと伝わっていく流れをイベントバブリングといいます。

イベントバブリングの利用

では、このイベントバブリングは、実際にはどんなふうに利用できるのでしょうか。例えば、複数の要素のイベントをまとめて監視する場合に使えます。つまり、


<!doctype html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <div>
      <p onclick="console.log('p');">test</p>
      <p onclick="console.log('p');">test</p>
      <p onclick="console.log('p');">test</p>
    </div>
  </body>
</html>

こういう場合、3つのp要素がそれぞれイベント属性を持つのは無駄です。そこで、以下のようにその親であるdiv要素でイベントを監視すれば、1つのイベント属性にまとめることができます。というのも、p要素で発生したイベントはイベントバブリングにより親のdiv要素に伝わるからです。


<!doctype html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <div onclick="console.log('p');">
      <p>test</p>
      <p>test</p>
      <p>test</p>
    </div>
  </body>
</html>

ちなみに、さっきからイベント属性を例として使っていますが、addEventListenerでJavaScriptからイベントを登録する場合も同じです。

ただし、実は、上に示した例のようにp要素のイベントをdiv要素に動かしただけでは不完全です。なぜなら、書き換える前の例では「div要素の中のp要素」をクリックしたときにイベントが発生するのに対し、書き換えたあとの例では、div要素の中のp要素以外の場所をクリックしても反応してしまうからです。

この問題点を直す方法は、三章第五回で解説します。今回はイベントバブリングの仕組みを紹介しました。