バブリングによるイベントの伝播addEventListenerの第三引数useCaptureを利用して問題を解決した例から、イベントの仕組みを解説していきます。壮フロントエンド・エンジニア次回このシリーズの記事一覧をみる目次addEventListenerとは今回のお題このお題での問題点イベントの伝播のしかたを理解するイベントの伝播を止めるここまでのまとめJavaScriptを書いていく上で必要な知識というのは、たくさんあります。それは例えば、JavaScriptの文法のルールであったり、ブラウザの挙動であったり、オブジェクト指向的な考え方であったり、jQueryやBackbone.jsなどの具体的なライブラリの使い方であったりします。このシリーズでは、基礎と言えば基礎に分類されるけれど、普段何気なく書いているがゆえに、そこまで意識しなかった仕様や、仕組みを改めて見直すことでJavaScriptに対する理解をより深められるようなトピックを選び、解説していきます。取り上げるテーマは、addEventListenerについてです。addEventListenerの第三引数useCaptureを利用し、問題を解決した例を紹介しながらイベントの仕組みを解説していきます。今回紹介するコードのサンプルは次のaddEventListenerサンプルリポジトリにまとめてあります。併せて参照してください。addEventListenerサンプルリポジトリcodegrid/2015-addEventListeneraddEventListenerとはまず、addEventListenerとはなんでしょう。これは基礎的な内容であるため、ここではサラッと解説するにとどめますが、addEventListenerとは、DOMの仕様で用意されている、イベントリスナーを登録するためのメソッドです。クリック、マウスオーバーなど、ブラウザ上ではさまざまなイベントが発生します。addEventListenerを使用すれば、これらのイベントが発生した時、指定したfunctionを実行することができます。このように、イベントの発生に合わせて実行させるfunctionのことを、イベントリスナーと呼びます。MDNaddEventListenerを利用した、ごく単純なデモを見てみましょう。});上記は、id属性がfooである要素(以降、#fooのように記述します)に、クリックイベントを設定しています。addEventListenerを利用してイベント名とイベントリスナーを指定すると、指定したイベントが発生した時、イベントリスナーの内容が実行されます。イベントリスナー内では、引数に渡されたイベントオブジェクトを介し、発生したイベントに関する種々の情報を得ることができます。clicked!」とアラートされ、その次に、クリックされた場所のページ上でのX座標が表示されます。このようにイベントを設定できるaddEventListenerですが、大昔から問題なく利用可能だったわけではありません。Explorerに実装されたのはバージョン9からであり、それ以前のバージョンのIEでは、attachEventという別のメソッドを利用し、ブラウザ上で起こるイベントを設定する必要がありました。いった面倒な状況があったがゆえに、簡単にイベントを扱えるようにしてくれるjQueryなどのライブラリが便利に利用されてきました。比較的新しい環境や、スマートフォンのブラウザを対象とする場合は、そのような旧IEを考慮する必要がありません。問題なくaddEventListenerを利用することができます。addEventListenerをそのまま使う場合、第三引数のuseCaptureを指定することで、より細かなイベント制御を行うことができるというのも、大きな利点です。true);このuseCaptureというものは、jQueryの.on()では対応していません。この機能が必要であれば、どのみちaddEventListenerをそのまま使う必要があります。いざというときにパッと使えるよう、addEventListenerとイベントの仕組みをおさらいしてみましょう。今回のお題addEventListenerの第三引数が便利なものであると述べましたが、正直なところ、これを使うケースはそれほど多くはないです。これを使わなければ解決できないケースというのは存在します。その場合、イベントの仕組みを正確に把握していないと四苦八苦するはずです。「素直に実装したら困ってしまった例」を見てみましょう。これを、今回解決するお題とします。次のデモは、デスクトップ環境で動作します。このデモには2つの機能が同居しています。ひとつは、カウンタ増加の機能。赤いdiv(#counter)をクリックすると、中に書かれた数字が+1されます。ひとつが、ドラッグ移動の機能。ピンクのdiv(#floater)は、ドラッグで好きな場所に移動することができます。クリックによるカウンタ増加とドラッグによる移動(ソースコード:02.clickProblem)ざっとコードを見てみましょう。#floaterのドラッグ部分は、mousedown、mousemove、mouseupで実装し、#counterのカウント増加の部分は、単純なclickで実装しています。floaterは絶対配置。topを更新してつまんだ位置へ移動。問題なさそうなこのデモですが、よくよく操作してみると、困ったことがひとつあることに気付きます。それは、ただドラッグ移動しただけでも、カウンタ増加が必ず発生してしまうということです。ピンクの部分をつまんでドラッグすればこのようなことは起こりませんが、赤い部分をドラッグすると発生します。#counter上でマウスのボタンを押し、そのまま動かし、ボタンを離すと、#counterがドラッグされたということになりますが、#floaterは#counterを内包しているため、同時に#floaterがドラッグされたことにもなります。#counter上でドラッグすれば、#coutnerでも#floaterでも「ドラッグされた」ことになります。ここで「ドラッグ」と言っているのは、mousedownしてmousemoveし、最後にmouseupされる一連の動作のことです。これは「クリック」と呼ばれる動作となります。#counterについて考えてみると、要素上でmousedownが発生し、次にmousemoveが発生しているものの、その後にまたmouseupが発生しています。ブラウザは、これを#counterでクリックイベントが発生したと捉えるようです。#counter上でドラッグすれば、#counterは必ずクリックされたことにもなるというわけです。2つの機能をそれぞれ作っているだけなのですが、ドラッグ時にカウント増加をさせないためには、何かしらの工夫が必要になります。ドラッグしたらマウスの位置がずれてるんだから、クリックにならないんじゃないの?」と筆者は思いました。DOMのspecを見たところ、p要素上でマウスをドラッグしてテキストを選択するような動作が行われた場合、mousedownもmouseupも、その要素上で起こったのであれば、ブラウザはクリックイベントを発生させるという旨の例が書かれていました。本稿の例も、ドラッグして要素が移動しているものの、mousedownもmouseupも同一の#counter上で発生しているので、クリックが発生しているということになるようです。clickイベントの伝播のしかたを理解する前途した問題は、発生したイベントがどのように伝わっていくかという仕組みを細かく把握していれば、解決することができます。そのためにはまず、「バブリング」について理解する必要があります。単純なデモで、イベントの基本的な動作を確認してみましょう。#div3と、三重の入れ子になったdivにそれぞれクリックイベントを設定し、自身のidをアラートするようにしています。#div1.」とアラートされます。#div2と#div3をクリックした場合は、自身に設定されたアラートが出るだけではありません。#div1.と2回、アラートされます。#div1.と3回、アラートされます。何かしらのイベントが発生し、そのイベントが複数の要素に関わる場合、ブラウザは、最も深い階層にある要素から順にイベントを評価します。ここで最も深い階層にあるのは#div3です。このため、#div3に設定したクリックのイベントリスナーが実行されました。次にこれを囲む#div2、その次は#div2を囲む#div1と、順々にイベントリスナーが実行されたのです。下位階層の要素から順々にイベントが評価されるこのデモのように、下位階層の要素から順々に上位階層のイベントが評価されることを、バブリングと言います。泡のようにポコポコと、下から上に上がって実行されるので、そのような名前なのではないかと思われます。この概念については、過去にCodeGridでも取り上げたことがあるので参考にしてみてください。CodeGridこのように入れ子になった要素において、イベントが順々に評価されることをイベントの伝播と言うことがあります。#div3がクリックされたあとに#div2、#div1と、イベントが伝わっていくような動作になるためです。イベントの伝播を止めるしかし、イベントの伝播をさせたくない場合もあります。今回の例では、#div3がクリックされたのであれば、#div2や#div1がクリックされたことにはしないでほしいということです。そのためにはイベントの伝播を止める必要があるので、イベントオブジェクトのstopPropagationというメソッドを実行します。デモを見てみましょう。このデモのHTMLは、先ほどのデモと全く同じで、異なるのはJavaScript内の、#div3に設定したイベントリスナーのみです。イベントの伝播を止める});このデモにおいて、#div1、#div2をクリックした時の挙動はひとつ前のデモと同様ですが、#div3をクリックした場合はちょっと違います。#div3.」とだけ表示されます。これは、#div3のクリックイベントリスナー内で、e.stopPropagation()が実行されているためです。e.stopPropagation()が実行された時点でイベントの伝播は止まるため、#div2、#div1に設定されたイベントリスナーは実行されなくなります。これがイベントの伝播を止める方法です。イベントの伝播を止めるここまでのまとめ次回以降、「ボタンをクリックするとカウンタ増加するが、ドラッグ移動した場合にはカウンタ増加をさせたくない」というお題を、addEventListenerの第三引数を利用して解決していきます。具体的な解決方法の前に、まず今回は発生したイベントがどのように伝わっていくかという仕組みを把握するために、「バブリング」をおさらいしました。次回は、addEventListenerの第三引数useCaptureの使い方を見ていきます。stopPropagationとuseCaptureを併用することで、イベントを柔軟に制御することができる様子を解説しましょう。今回のお題の問題解決の手がかりになるはずです。壮フロントエンド・エンジニアWeb制作会社、フリーランスを経て、株式会社ピクセルグリッドに入社。Webサイト、WebアプリケーションのHTML、CSS、JavaScript実装に携わってきた。受託案件を中心にフロント周りの実装、設計、テクニカルディレクションを行う。スケーラビリティを考慮したHTMLテンプレート設計・実装、JavaScriptを使った込み入ったUIの設計・実装を得意分野とする。jQuery入門』(技術評論社、2014年11月14日)がある。2013ベストセッションでは、全278セッション中、ベスト20セッションに選出。特定商取引法に基づく。