FixScrollで困ったイベントの再帰防止方法メモ

FirefoxのアドオンFixScrollでは、通常のスクロール機能を完全に隠蔽して、その上にFixScroll方式のスクロール機能を実装しています。


中でも困ったのがページ内リンクでした。
最初のバージョンでは未対応のままリリースしましたが、実際に自分で使う中でこの機能が働かないと非常に使いにくい。ということでなんとかバブリングの再帰防止対応しました。かなり強引な方法なので、正規なやり方ではないかもしれませんが、とりあえず共有。


簡単な構造はこちら

<tabbrowser>
  <stack>
    <browser/>
    <canvas/>
    <scrollbar/> //縦スクロールバー
  </stack>
<tabbrowser>

■前提

scrollbarの変更をbrowser,canvasの表示領域に反映する。
=scrollbarを変更するとbrowserがscrollする。

(1)リスナーの追加


ページ内リンクのイベントをキャプチャする方法のひとつはタグのscrollイベント。(他の方法を調べてみましたが、どのソースに書いてあるのか突き止めきれず。)それをbrowserに対して追加します。

browser.addEventListener('scroll', method, bool);


#上記のメソッド内でscrollbarが変更されるため、結局browserに再度scrollイベントが発生する。そのため、再帰的にイベントが発生してしまう。

(2)メソッド内の最初にリスナーの削除、最後にリスナーの追加

function method(){
  browser.removeEventListener('scroll', method, bool);
  //ここに処理内容を記載(scrollを発生させる処理が含まれる)
  browser.addEventListener('scroll', method, bool);
}


この場合でもscrollのイベントをキャプチャしてしまいます。
(method内での処理をトリガーとするリスナーはmethodが終わってから判定する?)

(3)最後にリスナー追加する部分をスレッドで処理させる

function method(){
  browser.removeEventListener('scroll', method, bool);
  //ここに処理内容を記載(scrollを発生させる処理が含まれる)
  window.setTimeout(function(){browser.addEventListener('scroll', method, bool);}, 0);
}


これは他のアドオンでどのように実装しているのか調べているときに見つけました。調べたアドオンはこちらFoxSplitterです。


こうすると、method内で処理した内容を元にキャプチャしなくなります。

(4)[bad know how] 最終イベント実行時を保持して、現在時刻と比較して処理の実行可否を判断


上記の処理が連続で起こる場合、タイミング次第ではイベントが発生してしまいます。method内でリスナー削除した後にスレッドがリスナー追加してしまうような状況です。


FixScrollアドオンでは、scrollイベントをトリガーにスクロール処理をするため、余分なイベントをキャプチャ=余分なスクロールとなり、避ける必要がありました。ページ内リンクが連続して行われることはないため、選択肢のひとつとなりえました。

function method(){
  var diff = new Date() - lastMethodTime;
  lastMethodTime = new Date();
  if(diff < 300) return;
  browser.removeEventListener('scroll', method, bool);
  //ここに処理内容を記載(scrollを発生させる処理が含まれる)
  window.setTimeout(function(){browser.addEventListener('scroll', method, bool);}, 0);
}


(4)の対応をしても、ページが重い場合には閾値をどうしても超えることもあるため、100%確実な方法とは言えません。とはいえ、ほとんどのケースでは(3)までの対応で何とかなると思います。