最近有个需求,移动端网站,列表上拉加载,点击详情后返回,每次都固定返回到顶部,感觉这样不够人性化,希望固定到进列表前的页面,于是简单实现了一下。
这里有两个问题
1、数据都是异步的
2、只有返回的时候定位(刷新正常回顶部)
简单的实现思路及主要代码
为了方便二次加载,异步数据每次缓存到本地,同时拦截页面所有链接,在即将跳转的时候记录当前页面或者滚动元素的scrollTop值,下次进页面判断是否返回进来的,如果是直接进来或者刷新,则重新请求,如果为返回,则直接使用已缓存数据迅速加载后使用已缓存的scrollTop值定位到进详情页之前的位置,同时清除值,即scrollTop值的缓存仅一次有效。
判断浏览器返回关键代码如下:
var navigationType = window.performance.navigation.type; var navigation = { enter: navigationType === 0, refresh: navigationType === 1, back: navigationType === 2, type: navigationType }
navigation.back为true的时候则为页面返回进入,当然这里兼容性并不是很好,但由于是移动端,普遍WebView内核,再加上个别浏览器如QQ、UC等返回实则直接使用缓存,触发不了JS运行,所以并不存在很大的问题,经测试Android、iOS皆可。
本地缓存,使用localStorage,但由于数据量可能比较大,这里对localStorage做了类似cookie的时效性封装,默认数据缓存30分钟,如果超时在取数据时直接返回null并移除已失效缓存,以此来降低缓存占用率,同时,每次进入页面实例化缓存封装的同时,异步扫描一次所有缓存,无效的自动移除。
至于输出,可以根据需要,对数据进行预处理或者干脆存储html,进入页面后首先对缓存进行加载,之后再做事件绑定等动作。由于我一开始上拉加载的页面都统一使用一个自己事先封装的组件,回调传回数据进行业务渲染,为了方便,我在组件内对数据进行缓存和重载,外部保持原有的业务渲染逻辑,只是每次请求数据前先获取缓存,存在则直接通过回调传回缓存数据,每次回调执行完毕后触发scrollTop,由于scrollTop传值大于当前页面长度情况,页面会滚动到底部,于是又会根据原有封装的上拉加载调用下一页的数据,如果存储的是第二页中某个位置,则可能出现第一次加载完滚动到底部立即加载第二页,然后继续触发scrollTop向底部滚动到目标位置,同理,如果存储的是第三、第四页也会持续向下滚动,于是,这里就出现了一个没法解决的问题,这样依次加载几页效果还好,如果十页、二十页,那么会有明显的滚动效果,不是很理想,由于页面数量多,且都使用了现成封装组件,这样改动量最小就暂时这样做了,最佳方案还是每次存储已加载的所有数据,甚至直接html,这样返回时一次性输出,滚动效果会达到最大限度的消除,但弊端是要在存储前对数据干涉,输出时又要对原有的输出进行干涉,大家可以权衡利弊。当然,大家也可以根据具体需要,将这种方案进一步封装,缓存的输出保持不影响原有程序,这里仅说出我的简单思路,不再赘述。
另外,如果30分钟后用户才触发缓存,这样的情况可以根据业务需要,适当增加缓存时效或者干脆做永久缓存,和坐标的存储、使用一样,使用一次直接清除,第一次之后每次进页面异步请求后默默将新数据存入缓存(如果数据变动快的话)。
注:这里不是也sessionStorage主要是因为实际应用中发现个别浏览器sessionStorage存在无故丢失甚至直接不可用等情况,所以直接采用localStorage
本地缓存简单实现代码如下:
/** * Created by William.Wei on 2016/5/5. * 参考js.cookie插件api,实现类似cookie的expires * var store = new Store([local|session]) * store.set('key',{expires:1}) * store.get('key') * store.getJSON('key') * store.remove('key') */ define(function (require, exports, module) { var global = window, encode = encodeURIComponent, decode = decodeURIComponent, expiresMap = { d: 86400000, h: 3600000, m: 60000, s: 1000 }, support = { local: global.localStorage, session: global.sessionStorage }; function compare(oldData, newData) { return oldData === newData; } function setValue(key, value, attributes) { var store = this.store; if (typeof value === 'undefined' && attributes.expires === -1) { try { store.removeItem(encode(key)); } catch (e) { } return; } attributes = $.extend(true, {}, this.defaults, attributes); if (typeof attributes.expires === 'number') { var expires = new Date(); var unit = expiresMap[attributes.unit] || 1; expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * unit); attributes.expires = expires; } try { var result = JSON.stringify(value); if (/^[\{\[]/.test(result)) { value = result; } } catch (e) { } if (attributes.compare && ($.isFunction(attributes.compare) ? attributes.compare : compare)(getValue.call(this, key), value)) { return false; } value = {v: value}; if (attributes.expires) { value.t = attributes.expires.getTime(); } try { store.setItem(encode(key), JSON.stringify(value)); } catch (e) { } return true; } function parse(key, value) { try { var temp = JSON.parse(value); if (!temp.t || temp.t > +new Date()) { value = temp.v; if (this.json) { value = JSON.parse(value); } } else { //无效移除 setValue.call(this, key, undefined, {expires: -1}); return; } } catch (e) { value = undefined; } return value; } function getValue(key) { var value, store = this.store; if (key) { value = parse.call(this, key, store.getItem(encode(key))); } else { value = {}; var len = store.length, tempKey; for (var i = 0; i < len; i++) { tempKey = store.key(i); value[tempKey] = parse.call(this, decode(tempKey), store.getItem(tempKey)); } } return value; } function Store(type) { this.store = support[type || 'local']; //延迟1s清理失效脏数据 setTimeout(function () { for (var i = 0; i < this.store.length; i++) { this.get(this.store.key(i)); } }.bind(this), 1000); } Store.prototype = { defaults: { expires: null }, get: getValue, set: setValue, getJSON: function () { return getValue.apply({ store: this.store, json: true }, arguments); }, remove: function (key, attributes) { setValue.call(this, key, undefined, $.extend(true, {}, attributes, { expires: -1 })); }, clear: function () { this.store.clear(); } }; module.exports = Store; });上一篇: Android优化之SparseArray替代HashMap 下一篇: 工作五年第一次,我失业了