起因

最近做的一个图片浏览功能,请自行脑补手机图库、微博查看图片等场景。
功能做好之后,马上有同学反应:进入图片浏览层时,如果用户触摸手机后退按钮,整个页面都会返回,能否在触摸返回时只关闭浏览层?

一般移动端会对 WebView 做一些默认的设置,比如 Hook 用户触摸后退按钮,调用 WebView 后退方法。既然如此,就可以利用这一点,通过操纵浏览记录使用户触摸后退关闭浏览层。

history

想要操纵浏览记录,就不得不提history对象了。通过常用的back()forward()go()方法就可以自由的控制浏览器跳转到任意一个历史记录。
而在 HTML5 中,history 又添加一些新的成员,允许你对浏览记录进行添加和修改却不会刷新页面,这就是解决问题的关键了。

  • history.pushState(stateObj, title, url) 用于创建新历史记录;
  • history.replaceState(stateObj, title, url) 用于修改当前历史记录;
  • history.state 读取当前状态,创建历史记录时会添加状态对象;
  • window.onpopstate 是一个事件,当历史记录被修改时会触发。

简单实现

<div id="container"></div>

就把这个 div 看作一个图片浏览层吧,样式脑补。

function Demo(id) {
this.el = document.getElementById(id);
}
Demo.prototype = {
constructor: Demo,
show: function() {
// 处理显示的代码...
this.el.classList.add('in');
},
hide: function() {
// 处理隐藏的代码...
this.el.classList.remove('in');
},
useState: {
show: function() {
// 参数1是 Object 类型,可以被 history.state 读取
// 参数2是标题,在一些浏览器中会被忽略,象征性的传空就可以了
// 参数3是新记录的地址,会在末尾追加,如:http://test.com/demo
history.pushState({ page: 'demo' }, '', '/demo');
// 调用真正的显示方法
this.show();
},
hide: function() {
// 返回上一条历史记录
history.back();
}
},
bindEvent: function() {
var _this = this,
handelShow = this.show,
handelHide = this.hide,
imgs = document.getElementsByTagName('img'),
// 假设图片浏览层上有一个 .back 后退按钮
back = document.getElementsByClassName('back')[0];
// 加个判断避免兼容性问题
if(history.pushState) {
// 如果可以使用 history API 就重新赋值 handelShow 和 handelHide
handleShow = this.useState.show.bind(this);
handleHide = this.useState.hide.bind(this);
// history.state 变化时触发,假设初始 URL = http://test.com
window.onpopstate = function(e) {
// 这里的 e.state 和 history.state 等价的
if(!e.state) {
// 无状态时隐藏
_this.hide();
console.log(document.location); // http://test.com
} else if(e.state.page === 'demo') {
// 如果是自己定义的状态就显示
_this.show();
console.log(document.location); // http://test.com/demo
}
};
}
// 给图片绑定事件
[].forEach.call(imgs, function(img){
img.addEventListener('touchstart', function() {
handelShow();
});
});
// 图片浏览层上的后退事件
back.addEventListener('touchstart', function() {
handelHide();
});
}
};
// 调用
new Demo('container');

执行history.pushState()时不会触发window.onpopstate,只有前进forward()、后退back()时才会触发,所以在之后还需要调用显示的方法。
对于不支持history.pushState的浏览器执行的还是原来的显示和隐藏方法,handleShowhandleHide在判断内才被重新赋值。
通过在window.onpopstate判断history.state来执行不同的操作,当然你也可以判断location

以上代码未经测试,只作思路展示,注释说明了一切。

应用场景

在 SPA 场景中,history 早已大显神通。与history.state相似的还有location.hash,也有其对应的事件window.onhashchange,一般用于处理 IE 低版本的兼容。
抛开本篇描述的功能需求,类似的场景也可以运用。比如左侧导航在原生APP都是可以通过触摸返回来关闭,在浏览器中也可以模拟这种操作体验。