最近看了很多关于无限滚动的文章,也在面试中被问到了,优化方案很多,本次针对其中一条优化方案做了一个实际的优化:预加载。
实例简介
之前一直对单页应用有兴趣,所以自己写了一个前端路由,相关的文章见这里,这个单页应用采取hash的方式实现路由。最终的实例页面见这里。仓库在这里是一个经典的单页应用。要做优化的就是主页的信息滚动。这些信息通过ajax从服务器端获取,这里为了方便,服务器端会一直返回数据,哪怕是重复的。
页面如下:
预加载原理
正常情况下我的一个ajax请求服务器端会返回最多10条信息,当用户滑动到页面底部,就会触发ajax请求,发送一条新的请求来获取信息。但是这样造成了用户的等待,但是如果用户空闲,就进行ajax的发送也是不行的,这个浪费了用户的流量。
需要采用一种检测手段,检测用户会不会继续往下看,如果会,就进行预加载,不必等到用户翻到最底部。这样可以最大程度的优化用户的体验
预加载实现
剩余内容的高度检测
理解了原理,其实预加载的核心,就是进行一个即将滚动到底部的一个检测。这里需要用到一些高度,我采用的是
- document.body.scrollTop:这个高度是指浏览器滚动的高度
- document.body.clientHeight:这个是用户视窗的高度
- document.body.offsetHeight:这个是body的高度,也就是整个文档的实际高度。
这三个高度,前两个加起来就是用户已经翻看的高度,通过与整个文档的高度的对比就能检测到剩余文档的高度,只要这个高度小于阈值(OFFSET),就进行ajax请求的发送,从而实现预加载
var body = document.body;
var height = body.offsetHeight-(body.scrollTop+body.clientHeight);
if(height<OFFSET){
//浏览器快滚动到底部了
}
scroll事件反复执行带来的性能问题
上述函数可以直接和scroll事件进行绑定,但是这样直接绑定会造成一些不好的影响:
- scroll的反复执行降低了页面的流畅度
- 当达到目标高度之后,每一次的滚动都符合预加载的要求,所以轻微滚动都会触发n次的预加载请求。
scroll事件优化方案
针对上述第一个问题,可以采用函数节流的方式进行优化,但是要注意,最后一次用户的滚动必须执行,因为有可能最后一次滚动进入了阈值,此时必须尽快执行预加载请求。关于节流的原理网上描述的比较多,此处给一个我实现的包装代码:
method.throttling2 = function(func,delay){
var inthrott = false;//节流
var timer;//保证最后一次执行
return function(){
var self = this;
var args = arguments;//保留上下文
if(!inthrott){//判断上一次是否执行完毕
func.apply(self,args);
inthrott = true;
setTimeout(function(){
inthrott = false;
},delay);
}else {
clearTimeout(timer);
timer = setTimeout(function(){
func.apply(self,args);
inthrott = false;
},500);//此处500可以进行加快,主要是希望能够尽快的执行最后一次
}
}
}
而要解决第二个问题,就必须要保证上一次ajax请求没有结束之前,不会进行下一次ajax请求。这个其实通过一个标识符就可以解决,默认情况下是true,当ajax请求发送开始,修改为false,此时高度的改变会触发scroll函数,但是函数内部会判断这个标识符,如果为false,就不会进行高度的检测以及下一个ajax请求的发送。而当ajax请求结束后,标志位回归true,从而用户的滚动就可以触发下次的预加载了。
最后的代码大概像这样:
//method是我定义的一些公共方法
method.addevent(window,'scroll',method.throttling2(scrollTop,1000))//绑定scroll函数,通过之前的节流函数对原函数进行包装
//高度检测函数,通过节流之后绑定在了scroll事件上
function scrollTop(){
var height1 = document.body.scrollTop+document.body.clientHeight;
var height2 = document.body.offsetHeight;
if(state){//检测标识变量
if(height2 - height1 < eleheight*3){
console.log('到达预加载阈值,开始预加载');
getInfo();
}
}
}
//包装ajax请求,加上标识变量
function getInfo(){
//从服务器端获取商家发布的新信息
state = false;
//发送实际的ajax请求
{state = true;}//此语句写入ajax的回调
}
总结
目前我在预加载的时候会进行一个输出,方便我的观察以及调试。最后大概用户距离底部300px左右的时候,会触发预加载,此时,用户的任何滚动先经过节流,然后再进行标识的检测,不会出现两次预加载同时出现的情况。达到了我预期的目标。
展望
我认为,现实中还有可能出现一种极端情况:就是用户不断的下滑,在查找之前的一条历史消息,此时,因为我的预加载的阻拦,其实在一定程度上限制了用户的滑动(每次只能同时进行一个ajax请求),这里应该有优化的空间,可以通过检测是否出现在可视区域来进行加载。
另外一个可以优化的点在于图片,如果我的作品里面不是框,而是图片,预加载的效果就不太好,需要图片的懒加载,也就是用户可视范围内才进行图片的加载。还有待后续进行改进