实现上来加载的关键其实就是获取文档高度,视口高度,然后通过判断用户在滑动到文档底部时发送数据请求,将新接收的数据填充到文档的尾部
首先需要我们了解的一些概念
- clientHeight,clientWidth
- offsetHeight,offsetWidth
- scrollHeight,scrollWidth
- scrollTop
这三个的兼容性时比较繁琐的,不同浏览器的处理方式都不相同,我以chrome的为准来大概说一下
client是各个浏览器处理都比较统一的,都是指元素的可视区域的宽/高,包含padding值,但不包含滚动条的宽/高
offset返回元素可视区域的宽/高,包含padding值,包含滚动条的宽/高
scroll返回文档的实际宽/高,包含元素的padding值
而scrollTop获取的则是滚动条滚动的距离,可以理解问当前位置距离文档顶部的距离
scrollTop的兼容性也比较恶心,有兴趣的可以看一下他们整理的兼容性图表
需要用的时候做一下兼容性处理即可
scrollTop = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop;
下面是一个上拉加载下拉刷新的插件
dropload
;(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;
// 上方是否插入DOM
me.upInsertDOM = false;
// loading状态
me.loading = false;
// 是否锁定
me.isLockUp = false;
me.isLockDown = false;
// 是否有数据
me.isData = true;
me._scrollTop = 0;
me._threshold = 0;
me.init(options);
};
// 初始化
MyDropLoad.prototype.init = function (options) {
var me = this;
//设置opts 将用户定义和默认值合并,如果用户已设定按照用户设定,如果用户未设定,按照默认值 类似 name=options.name|| ‘tom’
me.opts = $.extend(true, {}, {
// 滑动区域
scrollArea: me.$element,
// 上方DOM
domUp: {
domClass: 'dropload-up',
domRefresh: '↓下拉刷新',
domUpdate: '↑释放更新',
domLoad: '加载中...'
},
// 下方DOM
domDown: {
domClass: 'dropload-down',
domRefresh: '↑上拉加载更多',
domLoad: '加载中...',
domNoData: '暂无数据'
},
// 自动加载
autoLoad: true,
// 拉动距离
distance: 50,
// 提前加载距离
threshold: '',
// 上方function
loadUpFn: '',
// 下方function
loadDownFn: ''
}, options);
// 判断loadDownFn是否赋值了事件,如果有说明用户设定了上拉加载,预先在下面插入加载时的DOM
if (me.opts.loadDownFn != '') {
me.$element.append('' + me.opts.domDown.domRefresh + '');
//获取插入的DOM
me.$domDown = $('.' + me.opts.domDown.domClass);
}
//设定提前加载的距离
//判断是否存在底部加载的DOM,并且当前没有设定提前加载的距离
//计算提前加载距离
//否则按照之前设定的加载距离加载
if (!!me.$domDown && me.opts.threshold === '') {
// 默认滑到加载区2/3处时加载
me._threshold = Math.floor(me.$domDown.height() * 1 / 3);
} else {
me._threshold = me.opts.threshold;
}
// 判断滚动区域
//判定用户指定的scrollArea是否是win
if (me.opts.scrollArea == win) {
me.$scrollArea = $win;
// 获取文档高度
me._scrollContentHeight = $doc.height();
// 获取win显示区高度 —— 这里有坑 不同浏览器对clientHeight的兼容性不同
me._scrollWindowHeight = doc.documentElement.clientHeight;
} else {
me.$scrollArea = me.opts.scrollArea;
me._scrollContentHeight = me.$element[0].scrollHeight;
me._scrollWindowHeight = me.$element.height();
}
//判断如果文档高度小于窗口高度,那么添加数据填充窗口
fnAutoLoad(me);
// 窗口调整
$win.on('resize', function () {
clearTimeout(me.timer);
me.timer = setTimeout(function () {
if (me.opts.scrollArea == win) {
// 重新获取win显示区高度
me._scrollWindowHeight = win.innerHeight;
} else {
me._scrollWindowHeight = me.$element.height();
}
//判断如果文档高度小于窗口高度,那么添加数据填充窗口
fnAutoLoad(me);
}, 150);
});
// 绑定触摸触发touch视图art事件
me.$element.on('touchstart', function (e) {
//菊花图不存
if (!me.loading) {
//fnTouches 对e.touchs做兼容处理
fnTouches(e);
//执行touchstart事件
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();
// 滚动页面触发加载数据
if (me.opts.loadDownFn != '' && !me.loading && !me.isLockDown && (me._scrollContentHeight - me._threshold) <= (me._scrollWindowHeight + me._scrollTop)) {
loadDown(me);
}
});
};
// touches 兼容
function fnTouches(e) {
if (!e.touches) {
//e.originaEvent 是jq在对构造一个jq对象后jq创建的e对象对原始的e对象的引用
//根据jq源码,jq对象下的e.pageX和e.pageY和原生的是一样的,但是额外增加了对IE的兼容
e.touches = e.originalEvent.touches;
}
}
// touchstart
function fnTouchstart(e, me) {
// 记住触摸时的scrolltop值
me._startY = e.touches[0].pageY;
// 触摸点距离顶部的距离
me.touchScrollTop = me.$scrollArea.scrollTop();
}
// touchmove
function fnTouchmove(e, me) {
//pageY 相对于文档左上角y的距离,也可以说是实际运动的距离
me._curY = e.touches[0].pageY;
me._moveY = me._curY - me._startY;
//动态计算 判断滑动方向来设定当前状态是 down/up
if (me._moveY > 0) {
//下拉
me.direction = 'down';
} else if (me._moveY < 0) {
//上拉
me.direction = 'up';
}
var _absMoveY = Math.abs(me._moveY);
//如果当前用户设定了loadUpFn 当前滑动方向为down,window.scrollTop()<0 不是锁定状态
//上拉加载操作
//加载上方
if (me.opts.loadUpFn != '' && me.touchScrollTop <= 0 && me.direction == 'down' && !me.isLockUp) {
e.preventDefault();
me.$domUp = $('.' + me.opts.domUp.domClass);
// 如果加载区没有DOM
if (!me.upInsertDOM) {
me.$element.prepend('');
me.upInsertDOM = true;
}
fnTransition(me.$domUp, 0);
//如果拉动距离小于设定的拉动距离
// 下拉
if (_absMoveY <= me.opts.distance) {
me._offsetY = _absMoveY;
// todo:move时会不断清空、增加dom,有可能影响性能,下同
me.$domUp.html(me.opts.domUp.domRefresh);
// 指定距离 < 下拉距离 < 指定距离*2
} 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);
// 下拉距离 > 指定距离*2
} 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);
// 存在loadUpFn 下拉 当前状态 'down' 不是锁定状态
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;
//执行loadUpFn函数
me.opts.loadUpFn(me);
} else {
//在transtion动画结束时
me.$domUp.css({'height': '0'}).on('webkitTransitionEnd mozTransitionEnd transitionend', function () {
//将目前是否显示上方DOM状态设置为false
me.upInsertDOM = false;
//删除目前显示的DOM
$(this).remove();
});
}
//重置
me._moveY = 0;
}
}
// 如果文档高度不大于窗口高度,数据较少,自动加载下方数据
function fnAutoLoad(me) {
if (me.opts.loadDownFn != '' && me.opts.autoLoad) {
if ((me._scrollContentHeight - me._threshold) <= me._scrollWindowHeight) {
loadDown(me);
}
}
}
// 重新获取文档高度
function fnRecoverContentHeight(me) {
if (me.opts.scrollArea == win) {
me._scrollContentHeight = $doc.height();
} else {
me._scrollContentHeight = me.$element[0].scrollHeight;
}
}
// 加载下方
function loadDown(me) {
me.direction = 'up';
me.$domDown.html(me.opts.domDown.domLoad);
me.loading = true;
me.opts.loadDownFn(me);
}
// 锁定
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;
// 为了解决DEMO5中tab效果bug,因为滑动到下面,再滑上去点tab,direction=down,所以有bug
me.direction = 'up';
}
};
// 解锁
MyDropLoad.prototype.unlock = function () {
var me = this;
// 简单粗暴解锁
me.isLockUp = false;
me.isLockDown = false;
// 为了解决DEMO5中tab效果bug,因为滑动到下面,再滑上去点tab,direction=down,所以有bug
me.direction = 'up';
};
// 无数据
MyDropLoad.prototype.noData = function (flag) {
var me = this;
if (flag === undefined || flag == true) {
me.isData = false;
} else if (flag == false) {
me.isData = true;
}
};
// 重置
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);
fnAutoLoad(me);
} else {
// 如果没数据
me.$domDown.html(me.opts.domDown.domNoData);
}
}
};
// css过渡
function fnTransition(dom, num) {
dom.css({
'-webkit-transition': 'all ' + num + 'ms',
'transition': 'all ' + num + 'ms'
});
}
})(window.Zepto || window.jQuery);
/*
* 方法核心 loadDown()
*
* 在touch事件结束时执行fnTouchend函数
* 判断me.opts.loadUpFn != '' && me.touchScrollTop <= 0 && me.direction == 'down' && !me.isLockUp
* 成立那么执行下拉刷新
*
* 监听scroll事件
* touch同时会触发scroll事件
* (me._scrollContentHeight - me._threshold) <= (me._scrollWindowHeight + me._scrollTop)
* 通过判断当前window的scrollTop + 视口的高度 是否大于文档高度 - 添加的提示信息的高度 来判断是否应该触发下拉加载
* */