移动端翻页插件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 Function对象扩展之延时执行函数
Jul 06 Javascript
jquery验证表单中的单选与多选实例
Aug 18 Javascript
jquery判断RadioButtonList和RadioButton中是否有选中项示例
Sep 29 Javascript
javascript event在FF和IE的兼容传参心得(绝对好用)
Jul 10 Javascript
jquery UI Datepicker时间控件的使用方法(基础版)
Nov 07 Javascript
JS实现部分HTML固定页面顶部随屏滚动效果
Dec 24 Javascript
全面了解JS中的匿名函数
Jun 29 Javascript
jQuery中hover方法搭配css的hover选择器,实现选中元素突出显示方法
May 08 jQuery
vue2.0中goods选购栏滚动算法的实现代码
May 17 Javascript
基于js中的存储键值对以及注意事项介绍
Mar 30 Javascript
安装Node.js并启动本地服务的操作教程
May 12 Javascript
解决Vue项目中tff报错的问题
Oct 21 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
一个简单的自动发送邮件系统(三)
2006/10/09 PHP
php中函数的形参与实参的问题说明
2010/09/01 PHP
PHP迭代器的内部执行过程详解
2013/11/12 PHP
Symfony2安装的方法(2种方法)
2016/02/04 PHP
谈谈php对接芝麻信用踩的坑
2016/12/01 PHP
PHP字符串与数组处理函数用法小结
2020/01/07 PHP
菜单效果
2006/10/14 Javascript
jquery里的each使用方法详解
2010/12/22 Javascript
10个基于浏览器的JavaScript调试工具分享
2013/02/07 Javascript
删除节点的jquery代码
2014/01/13 Javascript
js生成动态表格并为每个单元格添加单击事件的方法
2014/04/14 Javascript
addEventListener 的用法示例介绍
2014/05/07 Javascript
jquery append 动态添加的元素事件on 不起作用的解决方案
2015/07/30 Javascript
Document.body.scrollTop的值总为零的快速解决办法
2016/06/09 Javascript
jQuery制作图片旋转效果
2017/02/02 Javascript
基于Bootstrap分页的实例讲解(必看篇)
2017/07/04 Javascript
简单的网页广告特效实例
2017/08/19 Javascript
js数据类型检测总结
2018/08/05 Javascript
vue-cli3.0 脚手架搭建项目的过程详解
2018/10/19 Javascript
vue router导航守卫(router.beforeEach())的使用详解
2019/04/19 Javascript
解决Vue-Router升级导致的Uncaught (in promise)问题
2020/08/07 Javascript
[00:50]2014DOTA2国际邀请赛 NEWBEE战队回顾
2014/08/01 DOTA
[01:14]英雄,所敬略同——2018完美盛典宣传视频
2018/12/05 DOTA
python标准算法实现数组全排列的方法
2015/03/17 Python
Python编程之多态用法实例详解
2015/05/19 Python
Python subprocess模块详细解读
2018/01/29 Python
Python 获取div标签中的文字实例
2018/12/20 Python
python文件操作的简单方法总结
2019/11/07 Python
浅谈Pytorch中的自动求导函数backward()所需参数的含义
2020/02/29 Python
大学生农村教师实习自我鉴定
2013/09/21 职场文书
建筑工程管理专业自荐信范文
2013/12/28 职场文书
创先争优活动方案
2014/02/12 职场文书
三好学生演讲稿范文
2014/04/26 职场文书
2014年大学生社会实践自我鉴定
2014/09/26 职场文书
英文诗歌翻译方法(赏析)
2019/08/16 职场文书
MySQL中dd::columns表结构转table过程及应用详解
2022/09/23 MySQL