移动端翻页插件dropload.js(支持Zepto和jQuery)


Posted in Javascript onJuly 27, 2016

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源代码 

js:

(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);

CSS:

.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;
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
脚本安需导入(装载)的三种模式的对比
Jun 24 Javascript
javascript中字符串拼接需注意的问题
Jul 13 Javascript
jQuery ready()和onload的加载耗时分析
Sep 08 Javascript
移动端js触摸事件详解
Sep 18 Javascript
详解jQuery的表单验证插件--Validation
Dec 21 Javascript
Angularjs分页查询的实现
Feb 24 Javascript
关于vuex的学习实践笔记
Apr 05 Javascript
打造通用的匀速运动框架(实例讲解)
Oct 17 Javascript
利用百度地图API获取当前位置信息的实例
Nov 06 Javascript
详解处理Vue单页面应用SEO的另一种思路
Nov 09 Javascript
eslint 的三大通用规则详解
May 16 Javascript
jQuery实现的记住帐号密码功能完整示例
Aug 03 jQuery
js中遍历对象的属性和值的方法
Jul 27 #Javascript
js中的关联数组与普通数组详解
Jul 27 #Javascript
20分钟打造属于你的Bootstrap站点
Jul 27 #Javascript
浅谈js多维数组和hash数组定义和使用
Jul 27 #Javascript
js插件dropload上拉下滑加载数据实例解析
Jul 27 #Javascript
js 定义对象数组(结合)多维数组方法
Jul 27 #Javascript
js创建数组的简单方法
Jul 27 #Javascript
You might like
php获取ip的三个属性区别介绍(HTTP_X_FORWARDED_FOR,HTTP_VIA,REMOTE_ADDR)
2012/09/23 PHP
php处理抢购类功能的高并发请求
2018/02/08 PHP
php中输出json对象的值(实现方法)
2018/03/07 PHP
基于CI(CodeIgniter)框架实现购物车功能的方法
2018/04/09 PHP
php的instanceof和判断闭包Closure操作示例
2020/01/26 PHP
JavaScript 字符串连接性能优化
2008/12/20 Javascript
PPK 谈 JavaScript 的 this 关键字 [翻译]
2009/09/29 Javascript
用js实现的自定义的对话框的实现代码
2010/03/21 Javascript
Jquery easyUI 更新行示例
2014/03/06 Javascript
js下将阿拉伯数字每三位一逗号分隔(如:15000000转化为15,000,000)
2014/06/02 Javascript
node.js中的path.dirname方法使用说明
2014/12/09 Javascript
js 原型对象和原型链理解
2017/02/09 Javascript
一步步教会你微信小程序的登录鉴权
2018/04/09 Javascript
vue下history模式刷新后404错误解决方法
2018/08/18 Javascript
详解vue 2.6 中 slot 的新用法
2019/07/09 Javascript
小程序登录之支付宝授权的实现示例
2019/12/13 Javascript
本地文件上传到七牛云服务器示例(七牛云存储)
2014/01/11 Python
跟老齐学Python之list和str比较
2014/09/20 Python
Python contextlib模块使用示例
2015/02/18 Python
python通过邮件服务器端口发送邮件的方法
2015/04/30 Python
举例讲解Python中的Null模式与桥接模式编程
2016/02/02 Python
Python 中字符串拼接的多种方法
2018/07/30 Python
Python版中国省市经纬度
2020/02/11 Python
python 函数嵌套及多函数共同运行知识点讲解
2020/03/03 Python
10张动图学会python循环与递归问题
2021/02/06 Python
Python抖音快手代码舞(字符舞)的实现方法
2021/02/07 Python
CSS3实现多背景模拟动态边框的效果
2016/11/08 HTML / CSS
打架检讨书500字
2014/01/29 职场文书
教师批评与自我批评(群众路线)
2014/10/15 职场文书
2014年卫生工作总结
2014/11/27 职场文书
简单的辞职信模板
2015/05/12 职场文书
公司管理建议书
2015/09/14 职场文书
微信小程序基础教程之echart的使用
2021/06/01 Javascript
关于PHP数组迭代器的使用方法实例
2021/11/17 PHP
详解Nginx的超时keeplive_timeout配置步骤
2022/05/25 Servers
python+opencv实现目标跟踪过程
2022/06/21 Python