zoukankan      html  css  js  c++  java
  • 移动端翻页插件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;
    }
  • 相关阅读:
    PHP 消息队列
    Nginx 设置负载均衡
    Nginx 服务器搭建
    PHP 获取文件扩展名的五种方式
    高并发和大流量解决方案
    <面试> PHP 常见算法
    Mysql 预查询处理 事务机制
    Linux定时任务 结合PHP实现实时监控
    Swoole 结合TP5搭建文字直播平台
    <记录> PHP Redis操作类
  • 原文地址:https://www.cnblogs.com/ccblogs/p/5257997.html
Copyright © 2011-2022 走看看