移动端翻页插件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;
}
原文地址:https://www.cnblogs.com/ccblogs/p/5257997.html