キャプチャリングとバブリングとpreventDefault()とstopPropagation()

ある要素に無名関数でイベントが設定されているけど、そのイベントを発生させたくない!でも要素は消したくない!ということがあったのでJavaScriptのイベントについて少し調べてみました。


具体的には、ある要素がtouchmoveの中でpreventDefault()しているためその要素の上だとタッチスクロールが出来なくて不便なのでどうにかしたいという状況でした。

preventDefault()

preventDefault()はその要素に設定されているブラウザの規定のアクションを中止する関数です。
タッチスクロールも規定のアクションに含まれます。
https://developer.mozilla.org/ja/DOM/event.preventDefault

イベントのキャプチャリングとバブリング

DOMのイベントには、キャプチャリングフェーズとバブリングフェーズという二つのフェーズがあって、addEventListenerの第三引数でどちらのイベントにするかを指定出来ます。
trueにするとキャプチャリングフェーズで実行されて、falseをしているするとバブリングフェーズで実行されます。デフォルトはfalseのバブリングフェーズです。

実行される順番

まずキャプチャリングフェーズが処理されたあとにバブリングフェーズが実行されます。

キャプチャリングフェーズ

DOMの外側(親要素)からイベントが発生した要素に向かってイベントが伝わっていきます。

バブリングフェーズ

イベントが発生した要素から外側に向かってイベントが伝わっていきます。

サンプル
<div id="outer">
  <div id="inner">
  </div>
</div>

こんな要素があったときにid="inner"でイベントが発生すると、

  1. bodyなど親要素のキャプチャリングフェーズのイベント
  2. id="outer"のキャプチャリングフェーズのイベント
  3. id="inner"のキャプチャリングフェーズのイベント
  4. id="inner"のバブリングフェーズのイベント
  5. id="outer"のバブリングフェーズのイベント
  6. bodyなど親要素のバブリングフェーズのイベント

という順番で処理が実行されます。


イベントの伝播順 - jsdo.it - share JavaScript, HTML5 and CSS

最初の問題を解決する

そんなわけで最初に書いてあった状態になったときには、外側の要素にstopPropagation()をキャプチャリングフェーズで呼び出すイベントを登録しておけば内側のイベントが発生しないので、preventDefault()が呼ばれなくてtouchmoveイベントによって画面がスクロールされてよかったという話でした。

stopPropagation()

これはイベントが伝播していくことをストップ出来る関数です。
https://developer.mozilla.org/ja/DOM/event.stopPropagation

スマートフォン

http://jsrun.it/koba04/gbGQ
スマートフォンで見るとtouchmoveの中でpreventDefault()が指定されているのにタッチスクロールが出来ると思います。

おまけ

outerのキャプチャリングフェーズでstopPropagation()したとき

forked: イベントの伝播順 - jsdo.it - share JavaScript, HTML5 and CSS

innerのバブリングフェーズでstopPropagation()したとき

forked: イベントの伝播順 - jsdo.it - share JavaScript, HTML5 and CSS



最初この方法が思いつかなくて、イベントを発生させたくない要素の上にposition:absolute, relativeを使ってdivをのせてイベントが伝わらないようにしようとしてました。。。