Android標準ブラウザでscrollTopが即座に更新されない

window.scrollByを実行した直後

スムーズスクロール系の処理が,Androidで動かないという話があったのでデバッグしました.

どうにも,Android 2.xにおける標準ブラウザではwindow.scrollByを実行した後に,document.body.scrollTopがすぐには更新されないみたいでした.20〜50msぐらいのタイマーを挟んであげると意図した値が取得できる感じ.

検証コード

大体こんな感じで検証できるんじゃないかと.

$(function() {
    $('#button').click(function() {
        var $self = $(this);
        window.scrollBy(0, -document.body.scrollTop);
        $self.after('<p>直後に取得: '+document.body.scrollTop+'</p>');
        setTimeout(function() {
            $self.after('<p>タイマ取得: '+document.body.scrollTop+'</p>')
        }, 50);
    });
});
<div style="width: 640px; height: 2048px; background: gray;">はこ</div>
<button id="button">一番上にスクロール</button>

scrollTopを使用した判定処理は遅延させてしまう

今回の修正対象になった処理は,いわゆる"ページの一番上に戻るボタン"でした・・・.lol

実際に修正した処理のサンプル

scrollTopの参照が必要な判定処理をタイマー的に遅らせました.本来は,タイマー処理内で即座に評価していたのを,次のタイマー処理の冒頭に遅延させることで対応しています.

/**
 * 指定座標までスクロールする
 * @param {Number} x
 * @param {Number} y
 * @parem {Number} m 移動間隔
 * @param {Number} k 移動量
 * @param {Function} callback
 */
function scrollToXy( x, y, m, k, callback ) {
  var lazyEvaluator;

  setTimeout(function () {
    if (lazyEvaluator && lazyEvaluator()) {
      if ( 'function' == typeof(callback) ) {
        callback();
      }
      return;
    }

    var left = document.body.scrollLeft;
    var top  = document.body.scrollTop;

    var h = Math.floor((-1 * (left - x) * k));
    var v = Math.floor((-1 * (top  - y) * k));
    window.scrollBy(h, v);

    // スクロール位置の評価は、つぎのタイマー処理に遅延させる
    // Android2.x〜3.xの標準ブラウザで、document.body.scrollXxxの更新が遅い
    // scrollBy直後に参照すると、更新されていないため条件を正しく評価できない
    lazyEvaluator = function () {
      return ((h == 0) || ((left + h) != document.body.scrollLeft))
          && ((v == 0) || ((top + v)  != document.body.scrollTop));
    };

    setTimeout(arguments.callee, m);
    return false;
  }, m);
};

変更前

参考までに変更前はこんな感じ.タイマー処理でscrollByを繰り返すシンプルな処理です.

function oldScrollToXy( x, y, m, k, callback ) {
  setTimeout(function () {
    var left = document.body.scrollLeft;
    var top  = document.body.scrollTop;

    var h = Math.floor((-1 * (left - x) * k));
    var v = Math.floor((-1 * (top  - y) * k));
    window.scrollBy(h, v);

    if ( 1
      && ((h == 0) || ((left + h) != document.body.scrollLeft))
      && ((v == 0) || ((top + v)  != document.body.scrollTop))
    ) {
      if ( 'function' == typeof(callback) ) {
        callback();
      }
      return true;
    }

    setTimeout(arguments.callee, m);
    return false;
  }, m);
};