uhyohyo.net

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

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

イベントバブリング

今回は、イベントバブリングについて解説します。ここがとても面白いところです。

次のサンプルを見てみましょう。

<!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
|
├――#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"
          

このようにその親にイベントが伝わります。ここで、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つのイベント属性にまとめることができます。

<!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要素でなくても反応してしまうからです。

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