キャプチャリングとバブリングと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"でイベントが発生すると、
- bodyなど親要素のキャプチャリングフェーズのイベント
- id="outer"のキャプチャリングフェーズのイベント
- id="inner"のキャプチャリングフェーズのイベント
- id="inner"のバブリングフェーズのイベント
- id="outer"のバブリングフェーズのイベント
- bodyなど親要素のバブリングフェーズのイベント
という順番で処理が実行されます。
最初の問題を解決する
そんなわけで最初に書いてあった状態になったときには、外側の要素に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をのせてイベントが伝わらないようにしようとしてました。。。