$(window).scrollをトリガーとしたイベント制御

2014-10-02

スマートフォンなどで、画面の下端まで行くと次のデータをロードする仕組みが有りますが、そんなjQueryプラグインを作っていた際に、次のデータをロードするイベントが重複してしまうという事態に陥ったので、少し考えてみました。

画面下端まで行くと次のデータをロードする

サンプル [1]

サンプルはブラウザをスクロールし、下端まで行くとLI部分を下に追加していくものです。下端に到達する度に追加されるので、無限になが~くなります。固定されたヘッダーにその回数が表示されます。

jQuery(document).ready(function(){
  var list = $("#list").html();
  
  $(window).scroll(function (e) {
    var $window = $(e.currentTarget),
        height = $window.height(),
        scrollTop = $window.scrollTop(),
        documentHeight = $(document).height(); 
     
  //下端までscrollしたら
    if (documentHeight === height + scrollTop) {
      var n = $("#headerArea").data('i');
          n++;
          $("#headerArea").text('下端まで行った回数:'+n);
          $("#headerArea").data('i',n);
          
          loadNext(list);
    }
  });
  
/*
 * リストへ追加する
 */
  var loadNext = function(list){
    $('#list').append(list);
  };
});

jQuery非同期通信を含める

サンプル[1]はさくさくっと追加されていきますが、これに少し重たいPHPへ非同期通信する処理を含めてみます。

サンプル[2]

$i = 1;
while ($i <= 100000000) : $i++; endwhile;

通信するPHP内には上記のように、100000000回ループして抜けるコードを書いておきます。1~2秒ほどの時間が掛かります。

jQuery(document).ready(function(){
  var list = $("#list").html();
  
  $(window).scroll(function (e) {
    var $window = $(e.currentTarget),
        height = $window.height(),
        scrollTop = $window.scrollTop(),
        documentHeight = $(document).height(); 
     
  //下端までscrollしたら
    if (documentHeight === height + scrollTop) {
      var n = $("#headerArea").data('i');
          n++;
          $("#headerArea").text('下端まで行った回数:'+n);
          $("#headerArea").data('i',n);

          loadNext(list);
    }
  });
  
/*
 * リストへ追加する
 */
  var loadNext = function(list){
      $.ajax({
          async:false,
          url: 'load.php',
          success: function(){
            $('#list').append(list);
          }
      });
  };
});

ロードするまで待機時間が有り、次が追加されているように見えますが、その待機時間の数秒の間に、ブラウザを上下に小さくスクロールすると回数が追加されるのが解るかと思います。下端に到達する度にAjaxが溜まっていく形になり、少しすると回数分が一気に追加されてしまいます。

これでは意味が有りませんので、同期通信にして通信が完了した後に次のAjaxクエリを発行出来るようにしたりなど、色々と手を考えてみたのですが、どうにも上手くいきません。

スクロールをトリガーとするならスクロールの数値で制御すれば良い

下端に行くという事がトリガーであるのならば、下端じゃない時は発生させなきゃいいだろう、という事ですね。

サンプル[3]

jQuery(document).ready(function(){
  var list = $("#list").html();
  
  $(window).scroll(function (e) {
    var $window = $(e.currentTarget),
        height = $window.height(),
        scrollTop = $window.scrollTop(),
        documentHeight = $(document).height(); 
        
        var sc = $("#headerArea").data('sc');
        if(scrollTop <= sc) return; //記録されているscrollTopと比較し、小さい場合は実行しない
        
       $("#headerArea").data('sc',scrollTop);//scrollTopをjQuery.dataで記録
     
  //下端までscrollしたら
    if (documentHeight === height + scrollTop) {
      var n = $("#headerArea").data('i');
          n++;
          $("#message1").text('下端まで行った回数:'+n);
          $("#headerArea").data('i',n);

        setTimeout(function(){
          loadNext(list);
        },3000);
    }
  });
  
/*
 * リストへ追加する
 */
  var loadNext = function(list){

      $.ajax({
          async:false,
          url: 'load.php',
          success: function(xml){
            $('#list').append(list);
          }
      });

  };
});

下端に到達した際にscrollTopの数値をjQuery.dataで記録しておき、その後、いくらスクロールしてもその数値と同じか小さい場合は次のクエリを発行しません。

ですがココで問題発生。

スマートフォンでスクロールするとアドレスバーが隠れるブラウザが有りますが、アドレスバー部分が隠れたり開いたりすると、差異が出て上手く動作しないケースに遭遇しました。

やはりAjaxを制御する方向で考える

サンプル[4]

jQuery(document).ready(function(){
  var list = $("#list").html();
  
  $(window).scroll(function (e) {
    var $window = $(e.currentTarget),
        height = window.innerHeight,
        scrollTop = $window.scrollTop(),
        documentHeight = $(document).height(); 
        
        var list_set = $('#list li').length / 10;
     
    //下端までscrollしたら
      if (documentHeight === height + scrollTop) {
        var n = $("#headerArea").data('i');
            n++;
            $("#headerArea").html('下端までスクロールした数:'+n+' / 現リストセット数:'+list_set);
            $("#headerArea").data('i',n);
            loadNext(list,n);
      }
  });
  
/*
 * リストへ追加する
 */
  var loadNext = function(list,n){
    var list_len = $('#list li').length; //現在のリストの数
    
   /*
    * 下までスクロールした数に現在のLIの数を掛けたものが
    * 現在のLIの数と一緒なら処理する
    */
    if( (list_len * n) === list_len ){ 
      $.ajax({
          async:false,
          url: 'load.php',
          success: function(xml){
            $('#list').append(list);
            $("#headerArea").data('i',0); //処理が終わったら初期化する
          }
      });
    }
  };
});

Ajax非同期通信を制御すると言っても、イベントを実行するトリガーがスクロールである以上は、Ajaxでどうこう出来るものでは無いって事です。do,thenでタスク処理しようがdeferredでどうこうしようが、何度も下端までスクロールされてしまえば発行されたタスクは実行されてしまいます。

ということで、下端に行く度にその回数を数え、表示しているLIの数が一緒なら実行するという方法を考えました。これならスマートフォンでも使えそうですね。