移動端翻頁插件dropload.js(支持Zepto和jQuery)


一. 聲明

  代碼來源:github上的dropload項目。

 

二. 問題

  dropload.js提供了最基本的上拉翻頁,下拉刷新功能。對於由服務端一次返回所有數據的情況基本通用。

  但是,需求往往不是服務端一次性返回所有數據,往往還要支持服務端分頁,搜索,排序,多條件篩選等功能。(比較類似美團美食的界面)

 

三. 解決方案。

  改進1:由於有分頁,搜索,排序,多條件篩選功能,可能都不需要上拉,進到頁面就沒有數據。

  例如:搜索一個服務端不存在的名字。

  所以,添加接口設置setHasData。

MyDropLoad.prototype.setHasData = function(ishasData) {
    var me = this;
    if (ishasData) {
      me.isData = true;
      me.$domDown.html(me.opts.domDown.domRefresh);
      fnRecoverContentHeight(me);
    } else {
      me.isData = false;
      me.$domDown.html(me.opts.domDown.domNoData);
      fnRecoverContentHeight(me);
    }
  };

  改進2:由以上問題還引發了一個bug,選擇不同的篩選條件,然后上拉加載更多,此時沒有反應了。

  原因較復雜,舉例說明:選擇不同的篩選條件,數據量不一樣,如果不執行resetload,那么頁面的的上拉計算距離就存在問題。

    1. 只要發API到服務端,無論返回成功失敗,都必須執行resetload,成功時需要在加載完全部新增的數據后resetload。

    2. 更改resetload如下,添加調用計算屏幕尺寸的方法。

MyDropLoad.prototype.resetload = function() {
    var me = this;
    if (me.direction == 'down' && me.upInsertDOM) {
      me.$domUp.css({ 'height': '0' }).on('webkitTransitionEnd mozTransitionEnd transitionend', function() {
        me.loading = false;
        me.upInsertDOM = false;
        $(this).remove();
        fnRecoverContentHeight(me);
      });
    } else if (me.direction == 'up') {
      me.loading = false;
      if (me.isData) {
        me.$domDown.html(me.opts.domDown.domRefresh);
        fnRecoverContentHeight(me);
      } else {
        me.$domDown.html(me.opts.domDown.domNoData);
      }
    }
  }

   3. 解決以上兩個問題,基本解決了90%的問題,還有一個是setHasData(false)之后的處理。(假設每頁的count時20條)

  bug: 在篩序條件1:返回20條數據,上拉加載更多返回10條數據,此時設置setHasData(false)。選擇篩選條件2,返回20條數據,上拉加載,你會驚奇的發現拉不動了。

  why: setHasData(false)之后狀態還停留在沒有更多數據的狀態。此時應該鎖定了上拉加載,更改篩選條件后,沒有解除鎖定,所以不能上拉加載了。

     解決方法:每次更改搜索條件,篩選條件,排序等時,都需要設置setHasData(true)。

 

四. 調用方法

  整體頁面邏輯較復雜。這里在整體解釋一遍。

  1. 選擇要上拉加載的DIV,添加調用方法。

    注意事項:

    (1)記得保存返回對象。

    (2)LoadDownFn時上拉加載后的回調,這里自己要處理的邏輯。我這里時翻頁發API,API參數中offset加20,然后發API。

    (3)無論API返回失敗成功,都必須resetload。

      這里強調:

        fetchData函數調用發API,失敗或者成功都必須self.moreFund.resetload()。

        並且失敗時直接調用self.moreFund.resetload()即可。成功時要在新的數據返回后,要先用JS動態加載HTML,加載完成后在執行self.moreFund.resetload()。

self.moreFund = $('#table-fundlist').dropload({
  scrollArea: window,
  domDown: {
    domClass: 'dropload-down',
    domRefresh: '<div class="dropload-refresh"><img class="drop-up-icon" src="images/dropload_up.png"><span>上拉加載更多</span></div>',
    domLoad: '<div class="dropload-load"><img class="loading-icon" src="images/droploading.gif"></div>',
    domNoData: ''
  },
  loadDownFn: function() {
    self.apiParams.offset += 20;
    self.fetchData(true);
  }
});

  2. setHasData詳解

    (1)什么時候需要設置true。

      當非翻頁API觸發之前。即選擇新的篩選條件,選擇新的搜索字段,選擇新的排序字段。這個時候必須setHasData(true)。

this.moreFund.setHasData(true);

    (2)什么時候設置false。

      服務端返回數據后,比較服務端返回的條目數與API發送的條目數是否一致,不一致設置setHasData(false)。

if (data.length < this.apiParams.count){           
  this.moreFund.setHasData(false);
  this.moreFund.lock();
}

  3. lock與unlock詳解

    (1)設么時候設置lock。

      服務端返回數據后,比較服務端返回的條目數與API發送的條目數是否一致,不一致設置lock()。

      當前頁面狀態不需要上拉加載時需要設置lock()。例如:在搜索框輸入的狀態。

    (2)什么時候設置unlock。

      只有一個地方需要調用。發送API之前設置unlock。

if (self.moreFund) {
  self.moreFund.unlock();
}

 

五. JS和CSS源代碼

;
(function($) {
  'use strict';
  var win = window;
  var doc = document;
  var $win = $(win);
  var $doc = $(doc);
  $.fn.dropload = function(options) {
    return new MyDropLoad(this, options);
  };
  var MyDropLoad = function(element, options) {
    var me = this;
    me.$element = $(element);
    me.upInsertDOM = false;
    me.loading = false;
    me.isLockUp = false;
    me.isLockDown = false;
    me.isData = true;
    me._scrollTop = 0;
    me.init(options);
  };
  MyDropLoad.prototype.init = function(options) {
    var me = this;
    me.opts = $.extend({}, {
      scrollArea: me.$element,
      domUp: {
        domClass: 'dropload-up',
        domRefresh: '<div class="dropload-refresh"><img class="drop-down-icon" src="../images/dropload_down.png"><span>下拉刷新</span></div>',
        domUpdate: '<div class="dropload-update"><img class="drop-up-icon" src="../images/dropload_up.png"><span>釋放更新</span></div>',
        domLoad: '<div class="dropload-load"><img class="loading-icon" src="../images/droploading.gif"></div>'
      },
      domDown: {
        domClass: 'dropload-down',
        domRefresh: '<div class="dropload-refresh"><img class="drop-up-icon" src="../images/dropload_up.png"><span>上拉加載更多</span></div>',
        domLoad: '<div class="dropload-load"><img class="loading-icon" src="../images/droploading.gif"></div>',
        domNoData: ''
          //domNoData  : '<div class="dropload-noData"><span>暫無數據</span></div>'
      },
      distance: 50, // 拉動距離
      threshold: '', // 提前加載距離
      loadUpFn: '', // 上方function
      loadDownFn: '' // 下方function
    }, options);

    if (me.opts.loadDownFn != '') {
      me.$element.append('<div class="' + me.opts.domDown.domClass + '">' + me.opts.domDown.domRefresh + '</div>');
      me.$domDown = $('.' + me.opts.domDown.domClass);
    }

    if (me.opts.scrollArea == win) {
      me.$scrollArea = $win;
      me._scrollContentHeight = $doc.height();
      me._scrollWindowHeight = doc.documentElement.clientHeight;
    } else {
      me.$scrollArea = me.opts.scrollArea;
      me._scrollContentHeight = me.$element[0].scrollHeight;
      me._scrollWindowHeight = me.$element.height();
    }

    $win.on('resize', function() {
      if (me.opts.scrollArea == win) {
        me._scrollWindowHeight = win.innerHeight;
      } else {
        me._scrollWindowHeight = me.$element.height();
      }
    });

    me.$element.on('touchstart', function(e) {
      if (!me.loading) {
        fnTouches(e);
        fnTouchstart(e, me);
      }
    });
    me.$element.on('touchmove', function(e) {
      if (!me.loading) {
        fnTouches(e, me);
        fnTouchmove(e, me);
      }
    });
    me.$element.on('touchend', function() {
      if (!me.loading) {
        fnTouchend(me);
      }
    });

    me.$scrollArea.on('scroll', function() {
      me._scrollTop = me.$scrollArea.scrollTop();
      fnRecoverContentHeight(me)
      if (me.opts.threshold === '') {
        me._threshold = Math.floor(me.$domDown.height() * 1 / 3);
      } else {
        me._threshold = me.opts.threshold;
      }
      if (me.opts.loadDownFn != '' && !me.loading && !me.isLockDown && me._threshold != 0 && (me._scrollContentHeight - me._threshold) <= (me._scrollWindowHeight + me._scrollTop)) {
        fnLoadDown();
      }
    });

    function fnLoadDown() {
      me.direction = 'up';
      me.$domDown.html(me.opts.domDown.domLoad);
      me.loading = true;
      me.opts.loadDownFn(me);
    }
  };

  function fnTouches(e) {
    if (!e.touches) {
      e.touches = e.originalEvent.touches;
    }
  }

  function fnTouchstart(e, me) {
    me._startY = e.touches[0].pageY;
    me.touchScrollTop = me.$scrollArea.scrollTop();
  }

  function fnTouchmove(e, me) {
    me._curY = e.touches[0].pageY;
    me._moveY = me._curY - me._startY;

    if (me._moveY > 0) {
      me.direction = 'down';
    } else if (me._moveY < 0) {
      me.direction = 'up';
    }

    var _absMoveY = Math.abs(me._moveY);

    if (me.opts.loadUpFn != '' && me.touchScrollTop <= 0 && me.direction == 'down' && !me.isLockUp) {
      e.preventDefault();

      me.$domUp = $('.' + me.opts.domUp.domClass);
      if (!me.upInsertDOM) {
        me.$element.prepend('<div class="' + me.opts.domUp.domClass + '"></div>');
        me.upInsertDOM = true;
      }
      fnTransition(me.$domUp, 0);
      if (_absMoveY <= me.opts.distance) {
        me._offsetY = _absMoveY;
        me.$domUp.html(me.opts.domUp.domRefresh);
      } else if (_absMoveY > me.opts.distance && _absMoveY <= me.opts.distance * 2) {
        me._offsetY = me.opts.distance + (_absMoveY - me.opts.distance) * 0.5;
        me.$domUp.html(me.opts.domUp.domUpdate);
      } else {
        me._offsetY = me.opts.distance + me.opts.distance * 0.5 + (_absMoveY - me.opts.distance * 2) * 0.2;
      }
      me.$domUp.css({ 'height': me._offsetY });
    }
  }

  // touchend
  function fnTouchend(me) {
    var _absMoveY = Math.abs(me._moveY);
    if (me.opts.loadUpFn != '' && me.touchScrollTop <= 0 && me.direction == 'down' && !me.isLockUp) {
      fnTransition(me.$domUp, 300);

      if (_absMoveY > me.opts.distance) {
        me.$domUp.css({ 'height': me.$domUp.children().height() });
        me.$domUp.html(me.opts.domUp.domLoad);
        me.loading = true;
        me.opts.loadUpFn(me);
      } else {
        me.$domUp.css({ 'height': '0' }).on('webkitTransitionEnd transitionend', function() {
          me.upInsertDOM = false;
          $(this).remove();
        });
      }
      me._moveY = 0;
    }
  }

  // 重新獲取文檔高度
  function fnRecoverContentHeight(me) {
    if (me.opts.scrollArea == win) {
      me._scrollContentHeight = $doc.height();
    } else {
      me._scrollContentHeight = me.$element[0].scrollHeight;
    }
  }

  MyDropLoad.prototype.lock = function(direction) {
    var me = this;
    if (direction === undefined) {
      if (me.direction == 'up') {
        me.isLockDown = true;
      } else if (me.direction == 'down') {
        me.isLockUp = true;
      } else {
        me.isLockUp = true;
        me.isLockDown = true;
      }
    } else if (direction == 'up') {
      me.isLockUp = true;
    } else if (direction == 'down') {
      me.isLockDown = true;
    }
  };

  MyDropLoad.prototype.unlock = function() {
    var me = this;
    me.isLockUp = false;
    me.isLockDown = false;
  };

  MyDropLoad.prototype.setHasData = function(ishasData) {
    var me = this;
    if (ishasData) {
      me.isData = true;
      me.$domDown.html(me.opts.domDown.domRefresh);
      fnRecoverContentHeight(me);
    } else {
      me.isData = false;
      me.$domDown.html(me.opts.domDown.domNoData);
      fnRecoverContentHeight(me);
    }
  };

  MyDropLoad.prototype.resetload = function() {
    var me = this;
    if (me.direction == 'down' && me.upInsertDOM) {
      me.$domUp.css({ 'height': '0' }).on('webkitTransitionEnd mozTransitionEnd transitionend', function() {
        me.loading = false;
        me.upInsertDOM = false;
        $(this).remove();
        fnRecoverContentHeight(me);
      });
    } else if (me.direction == 'up') {
      me.loading = false;
      if (me.isData) {
        me.$domDown.html(me.opts.domDown.domRefresh);
        fnRecoverContentHeight(me);
      } else {
        me.$domDown.html(me.opts.domDown.domNoData);
      }
    }
  };

  function fnTransition(dom, num) {
    dom.css({
      '-webkit-transition': 'all ' + num + 'ms',
      'transition': 'all ' + num + 'ms'
    });
  }
})(window.Zepto || window.jQuery);
.dropload-up,
.dropload-down {
  background-color: #F0EFF5;
  position: relative;
  height: 0;
  overflow: hidden;
}

.dropload-down {
  height: 50px;
  border-top: 1px solid #e5e5e5;
}

.dropload-refresh .drop-up-icon,
.dropload-refresh .drop-down-icon,
.dropload-update .drop-up-icon,
.dropload-update .drop-down-icon {
  vertical-align: text-bottom;
  margin-right: 3px;
  height: 16px;
  width: 12px;
}

.dropload-load .loading-icon {
  vertical-align: middle;
  height: 20px;
  width: 20px;
}

.dropload-refresh span,
.dropload-update span {
  vertical-align: middle;
  line-height: 18px;
  font-size: 16px;
  color: #585858;
}

.dropload-noData {
  border-bottom: 1px solid #e5e5e5;
  background-color: #FFFFFF;
}

.dropload-noData span {
  line-height: 18px;
  font-size: 14px;
  color: #999999;
}

.dropload-refresh,
.dropload-update,
.dropload-load,
.dropload-noData {
  position: absolute;
  width: 100%;
  height: 50px;
  bottom: 0;
  line-height: 50px;
  text-align: center;
}

.dropload-down .dropload-refresh,
.dropload-down .dropload-update,
.dropload-down .dropload-load {
  top: 0;
  bottom: auto;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM