移动端翻页插件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 相关文章推荐
JavaScript 在线压缩和格式化收藏
Jan 16 Javascript
js获取class的所有元素
Mar 28 Javascript
javascript利用apply和arguments复用方法
Nov 25 Javascript
20分钟轻松创建自己的Bootstrap站点
May 12 Javascript
JS读取XML文件数据并以table形式显示数据的方法(兼容IE与火狐)
Jun 02 Javascript
基于vue2.x的电商图片放大镜插件的使用
Jan 22 Javascript
浅谈在react中如何实现扫码枪输入
Jul 04 Javascript
Vue+Express实现登录状态权限验证的示例代码
May 05 Javascript
vue 组件中使用 transition 和 transition-group实现过渡动画
Jul 09 Javascript
解决layer.msg 不居中 ifram中的问题
Sep 05 Javascript
在vue中使用axios实现post方式获取二进制流下载文件(实例代码)
Dec 16 Javascript
React学习之受控组件与数据共享实例分析
Jan 06 Javascript
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使用memcache存储session的详解
2013/06/25 PHP
php以fastCGI的方式运行时文件系统权限问题及解决方法
2015/05/11 PHP
DWZ+ThinkPHP开发时遇到的问题分析
2016/12/12 PHP
HR vs ForZe BO3 第二场 2.13
2021/03/10 DOTA
绑定回车enter事件代码
2014/05/18 Javascript
JS根据年月获得当月天数的实现代码
2014/07/03 Javascript
在浏览器中实现图片粘贴的jQuery插件-- pasteimg使用指南
2014/12/29 Javascript
JS输入用户名自动显示邮箱后缀列表的方法
2015/01/27 Javascript
深入理解JavaScript中的call、apply、bind方法的区别
2016/05/30 Javascript
json的使用小结
2016/06/08 Javascript
轻松掌握JavaScript代理模式
2016/08/26 Javascript
探索Javascript中this的奥秘
2016/12/11 Javascript
Angular 2.x学习教程之结构指令详解
2017/05/25 Javascript
JS设计模式之访问者模式定义与用法分析
2018/02/05 Javascript
vue2.0安装style/css loader的方法
2018/03/14 Javascript
vue数据控制视图源码解析
2018/03/28 Javascript
解决vue.js提交数组时出现数组下标的问题
2019/11/05 Javascript
vue中使用WX-JSSDK的两种方法(推荐)
2020/01/18 Javascript
[49:08]OpTic vs Serenity 2018国际邀请赛小组赛BO2 第一场 8.18
2018/08/19 DOTA
Python enumerate遍历数组示例应用
2008/09/06 Python
Python中的列表生成式与生成器学习教程
2016/03/13 Python
Python实现简单文本字符串处理的方法
2018/01/22 Python
Sanic框架请求与响应实例分析
2018/07/16 Python
Python字符串的15个基本操作(小结)
2021/02/03 Python
Nike比利时官网:Nike.com (BE)
2019/02/07 全球购物
客服文员岗位职责
2013/11/29 职场文书
争先创优公开承诺书
2014/08/30 职场文书
三峡导游词
2015/01/31 职场文书
2015年党员创先争优公开承诺书
2015/04/27 职场文书
2015年度企业工作总结
2015/05/21 职场文书
2015年试用期工作总结范文
2015/05/28 职场文书
网络研修心得体会
2016/01/08 职场文书
pytorch中F.avg_pool1d()和F.avg_pool2d()的使用操作
2021/05/22 Python
欧元符号 €
2022/02/17 杂记
什么是SOLID
2022/03/24 Javascript
《地。-关于地球的运动-》单行本第七集上市,小说家朝井辽献上期待又害怕的推荐文
2022/03/31 日漫