手机端优化列表滚动性能——分页加载
现在在手机端列表滚动随处可见,拿现在大家都在用的微信来说,朋友圈就是一个列表,和好友发信息界面就是一个列表。
如果列表长度不长,比如你微信朋友圈一共就只有两三个人分享过他们的动态,那没问题,如果你是大屏手机估计还不用滚动就已经看完了。
但那有可能吗?有也只是少数。我们现在每天刷朋友圈那都是几屏几屏这样刷的。
我们设定一屏有五个人分享的动态,每一个动态里面有5个dom节点,即一屏有25个dom节点。
我们开始刷朋友圈,才看了四屏就已经100个节点了,再往下刷成百甚至上千个dom节点都出来了,那你手机岂不是卡成翔了。
但我们现实中刷朋友圈会越往下刷越卡吗?不会。为什么呢?
这就是今天要说的一个优化手机端列表滚动性能的方法——分页加载
什么是分页加载呢,大致意思就是把一个列表分成很多很多页,我一开始只给你显示若干页(N),当我往下滚动到底部的时候,显示下一页,同时把最上面那一页移除。
同样道理,往上滚动时则显示上一页,同时把最下面那一页移除。这样,显示的就永远只有N页,dom节点也就永远只有Nx25个。
那么分页加载需要处理哪些问题呢?
1. 往下滚动的时候把最上面一页移除,那等我要往上滚动的时候,上一页都已经被移除掉了,我怎么获取到上一页的内容呢?
2. 既然有dom节点的移除和添加,那么我的滚动条位置要移除页面的同时要怎么设置才能让用户看起来像是无缝连接呢?
3. 我如何判断我已经滚动到了页面的顶部或底部?
4. 在滚动到底部或顶部的时候,我们具体究竟要执行哪些操作。
我们一个个来解决。
第一个问题我们只需要用两个数组来存取被移除掉的内容就OK了。
var preRemoveArray = []; //被移除的当前页面之前的页面 var nextRemoveArray = []; //被移除的当前页面后面的页面
当我们要移除上一页的时候,只需要获得第一页的html,将它push进preRemoveArray就可以了。同时如果nextRemoveArray中有内容的话(意味着你之前已经浏览过下面的内容),只需要执行
nextRemoveArray.pop(nextRemoveArray.length - 1);
就可以获取到下一页的内容了
第二个问题,关于滚动条的位置,我们分两种情况。
1.往下拉
我们通过document.body.scrollTop获得当前滚动条的位置,因为往下拉要移除第一页的内容,所以
document.body.scrollTop = document.body.scrollTop - 第一页的高度
2.往上拉
假设我们现在页面中显示的是第2、3页,此时滚动到顶部,document.body.scrollTop = 0,加载了上一页的内容,移除了第3页之后,页面中显示的是第1、2页,
我们的滚动条要设置在第2页的顶部,这样用户看起来才像是无缝连接的,所以
document.body.scrollTop = document.body.scrollTop + 第一页的高度
3.判断是否到顶部很简单
var isTop = function() { return document.body.scrollTop === 0; };
返回true就是到顶部了
判断是否到底部需要获取几个值,
一个是document.body.scrollTop,当前滚动条的位置
一个是window.screen.height,当前屏幕的高度
一个是document.body.clientHeight, 当前整个页面的总高度
只有当document.body.scrollTop + window.screen.height = document.body.clientHeight时,即
var isBottom = function() { return document.body.scrollTop + window.screen.height == document.body.clientHeight; };
返回true时,则滚动到了底部。
4. 在滚动到顶部的时候,我们需要判断preRemoveArray里是否有值,有则意味着前面有页面,没有则意味着这是第一页,第一页的话我们不需要做任何处理。
如果不是第一页的话,我们要执行
1). 获取当前列表中的最后一页,并存进nextRemoveArray数组里
2). 将上一页的内容添加进列表中
3). 设置滚动条的位置
4). 将最后一页移除
往下滚动也是同一个道理,只是无论nextRemoveArray里面是否有值我们都要进行相应的操作。
有值则去nextRemoveArray里上一页的内容添加进列表
没有值则生成新的节点,添加到列表中。
最终代码如下:
var PAGE = 1; //初始化页数 var PAGESIZE = 5; //每页展示几条数据 var $list = $('#list'); //列表 var preRemoveArray = []; //被移除的当前页面之前的页面 var nextRemoveArray = []; //被移除的当前页面后面的页面 var init = function() { initPage(); initEvent(); }; var initPage = function() { renderData(); }; var initEvent = function() { var $page; var length; window.onscroll = function() { if (isTop()) { if (preRemoveArray[0]) { length = preRemoveArray.length; $page = $($('.page')[$('.page').length - 1]); //获取当前列表中显示的最后一页 nextRemoveArray.push($page[0].outerHTML); //将最后一页内容数组 $list.prepend(preRemoveArray.pop(length - 1)); //将上一页内容添加进列表 document.body.scrollTop = document.body.scrollTop + $($('.page')[0]).height(); //设置滚动条位置 $page.remove(); //移除最后一页 } } else if (isBottom()) { $page = $($('.page')[0]); //获取当前列表中显示的第一页 preRemoveArray.push($page[0].outerHTML); //将第一页内容数组 if (nextRemoveArray[0]) { //如果已经浏览过下面的内容 length = nextRemoveArray.length; $list.append(nextRemoveArray.pop(length - 1)); //将下一页内容添加进列表 } else { //如果没有浏览过下面的内容 renderSinglePage(); } document.body.scrollTop = document.body.scrollTop - $page.height(); //设置滚动条位置 $page.remove(); //移除第一页 } }; }; var renderData = function() { var innerHTML = ''; //我设定它永远只显示两页,所以一开始先加载两页数据出来 for (var i = 0; i < 2; i++) { innerHTML += getData(); } $list.append(innerHTML); }; var renderSinglePage = function() { var innerHTML = ''; innerHTML += getData(); $list.append(innerHTML); }; var getData = function() { var innerHTML = ''; innerHTML += '<div class="page page-' + PAGE + '">'; for (var i = 0; i < PAGESIZE; i++) { innerHTML += '<li>' + PAGE + '</li>'; //为方便看的清楚,我们给每行数据标记它是属于第几页的 } innerHTML += '</div>'; PAGE++; return innerHTML; }; var isBottom = function() { return document.body.scrollTop + window.screen.height == document.body.clientHeight; }; var isTop = function() { return document.body.scrollTop === 0; }; init();
PS: 为了方便开发,上面的代码引入了zepto.js