问题背景
在单页应用中,翻页一般通过display:none将先前的面板(一般就是个div容器)隐藏,然后将本次需要展现的面板设置成display:block(当然,还可能加点css切换动画,不过不影响我们本次的讨论结果,故不予关注),一般情况下,这样的处理方式是没啥问题的,不过如果之前的面板本身有滚动条,那么跳转到新面板之后再返回到原先面板就会导致滚动条位置直接变成0,这主要是当元素的display为none时,元素不占据位置,等到display非none的时候才会被重新布局,渲染。
我们以jq.ui(一个用于构建jqMobi应用的用户界面库)为例说明下怎么解决单页应用中切换页面导致的滚动条位置信息丢失问题
思路
在老页面被display:none之前用一个堆栈存储下当前页面的滚动条位置,然后在用户点击浏览器返回按钮的时候取出栈顶记录的滚动条位置信息并调用window.scroll滚动到指定位置即可。
基于jq.ui的实例
1、在jq.ui源码中页面切换之前手动触发个事件(用于在自己的代码中捕捉此事件并记录老页面滚动条位置信息)
/*在老的panel被display:none之前触发beforeHideOldPanel事件,
*用于记录当前滚动条位置,以便于返回上一页时滚动到指定位置*/
jq(oldDiv).trigger('beforeHideOldPanel');
2、在页面上监听第一步中触发的beforeHideOldPanel事件和popstate(浏览器回退事件)以及loadpanel(加载面板事件),当beforeHideOldPanel事件被触发且当前不在回退时记录滚动条位置,当loadpanel事件被触发且当前正在回退时从堆栈中取出滚动条位置并调用window.scroll手动滚动到指定位置
// 由于jq.wow.js中通过display:none方式切换面板,导致老的panel滚动条信息丢失
// (display:none元素无高度),点击返回上一页时会自动定位到顶部
// 此处通过一个简单的堆栈记录老页面的滚动条信息,退回上一页时手动调用window.scroll滚动到指定位置
function resetScrollWhenPopstate() {//借助数组实现个简单的堆栈var scrollStack = {list: [],push: function (obj) {this.list.push(obj);},pop: function () {return this.list.pop();}};$('.panel').on('beforeHideOldPanel', function (e) {// 仅非回退时才记录滚动条位置if (!isPopStating) {scrollStack.push({oldPageId: e.currentTarget.id,oldScrollTop: document.documentElement.scrollTop || document.body.scrollTop});}}).on('loadpanel', function (e) {// 仅回退页时才恢复滚动条位置if (isPopStating) {var obj = scrollStack.pop();if (obj && obj.oldPageId == e.currentTarget.id) {window.scroll(0, obj.oldScrollTop);}}});// 标示是否正在回退var isPopStating = false;window.addEventListener('popstate', function () {isPopStating = true;setTimeout(function () {isPopStating = false;}, 200);});
}
总结
看似简单的堆栈其实还是有挺大用处的,算法和数据结构这东西看来还是需要学习学习(@ο@) 哇~