一直以为监听返回按键是 H5 做不到的事情,其实是可以做到的。所以记录一下,深究一下。
最近在工作中也接触到这个需求,需要在不同的情况下达到能正常返回或是跳转到其他页面(但是我想这个只能在多页应用实现,单页应用应该是不可以的,没有去尝试过)。
浏览器前进、后退使用机制
用户点击浏览器工具栏中的后退按钮,或者是移动设备上的返回键的时候,或者是 JS 执行history.go(-1)
的时候,浏览器会在当前窗口“打开”历史记录中的前一个页面。不同的浏览器在“打开”前一个页面的表现并不同意,这和浏览器的实现以及页面本身的设置都有关系。在浏览器中,“后退到前一个页面”意味着:前一个页面的 html/js/css 等等的静态资源的请求(甚至是 ajax 动态接口请求)根本不会重新发送,直接使用缓存的响应,而不管这些静态资源响应的缓存策略是否被设置了禁用状态。
history 中的操作
-
window.history.back()
:后退 -
window.history.dorward()
:前进 -
window.history.go(num)
:前进或后退指定数量历史记录 window.history.pushState(state,title,url)
:在页面中创建一个 histor 实体,直接天剑到历史记录。- state:存储一个对象,可以添加相关信息,可以使用 history.state 读取其中的内容。
- title:历史记录的标题。
- url:创建的历史记录的链接,进行历史记录操作时会跳转到改链接。
-
window.history.replaceState()
:修改当前的 history 实体。 -
popstate
事件:history 实体改变时触发的事件。 -
window.history.state
:会获得 history 实体中的 state 对象。
popstate
popstate 只会在浏览器某些行为下触发,比如点击后退、前进按钮。
在微信浏览器中,从一个 HTML 跳到另一个 HTML 页面后,点击浏览器返回按钮,或者在第二个页面中调用history.back()
等返回上一页的方法,在安卓中会直接返回上一页(相当于重新加载上一页的所有内容,js 会重新执行一遍),但苹果手机中,范湖上一页是,浏览器会读取缓存中的页面内容,js 不会重新执行,在此进入这个页面不会触发 onload 事件。
1 | //强制刷新: |
pageshow
onpageshow 事件在页面显示时触发,如果页面不在“往返缓存”中,改时间会在 onload 后触发,在 onpageshow 事件中,可以利用 event.persisted
1 | window.addEventListener('pageshow'.function(evernt){ |
unload
指定 unload 事件处理程序的页面会被自动排除在“往返缓存”之外,即使事件处理程序是空白的,原因在于,unload 最长用于撤销 load 中所执行的操作,而跳过 load 后再次显示页面很有可能会导致页面不正常。
实现返回键监听
1 | function pushHistory() { |
这样实现的代码在移动端可以监听浏览器返回按键、物理返回按键、虚拟返回按键、手势返回按键等。
history.state 中包含了 state 的 一份拷贝,可以使用 history.state 读取其中的内容。
history.pushState
在当前页面创建并激活新的历史记录。
当调用 history.pushSate 或者是 history.replaceSate()不会触发 popstate 事件。只有做出后退的动作(执行 history.back()、history.go(-1))时才会执行该事件。另外,该事件只针对同一个文档,如果浏览历史的切换,导致加载不同的文档,该事件也不会触发。
在百度的控制台输入以下代码:
window.history.pushState(null, null, "https://www.baidu.com/?name=orange");
地址显示是https://www.baidu.com/name/orange,注意这里的url是不支持跨域的,跨域会报错.
history.pushState()只会改变当前地址的路径,并不会更新页面内容,只是产生了浏览器历史记录,此时点击返回按钮就返回到本页面,且也没有刷新,而且可以前进到https://www.baidu.com/?name=orange 页面
当我们在历史记录中切换的时候就会触发 popstate 事件。
pushSate 三个参数:
- state,状态对象,状态对象 state 是一个 JS 对象,通过 pushSate()创建新的历史记录条目。无论什么时候用户导航到新的状态,popstate 时间就会被触发,且该时间的 state 属性包含该历史记录条目状态对象的副本。
- title,历史记录的标题
- url,创建的历史记录的链接。进行历史记录操作时会跳转到该链接。
popstate
popstate 事件,history 实体(hostory.state)改变时触发的事件。
当活动的历史条目被更改时,将触发 popstate 事件。如果被激活的历史记录条目是通过对 history.pushState()的调用创建的,或者受到 history.replaceSate()的调用的影响,popstate 的 state 属性包含历史条目的状态的对象的副本——MDN
popstate 每当活动历史记录在同一个文档的两个历史记录条目之间发生变化时,就会将事件分派到窗口。如果激活的历史记录条目是通过调用 history.pushState()创建的,或者受到 history.replaceState()调用的影响,则该 popstate 事件的 state 属性包含历史记录项的状态对象的副本。
注意:用于处理 popstate 事件的浏览器在页面加载时的方式不同,但现在它们的行为相同。Firefox 从未在页面加载时发出 popstate 事件,Chrome 直到版本 34 才可以,而 Safari 一直到版本 10.0。
往返缓存
默认情况下,浏览器会在当前会话(session)缓存页面,当用户点击“前进”或“后退”按钮时,浏览器就会从缓存中加载页面(除去 meta 中加 no-cache 的情况)。
浏览器有一个特性叫“往返缓存”(back-forward cache 或 bfcache),可以在用户使用浏览器的“后退”和“前进”按钮时加快页面的转换速度。这个缓存中不仅保存着页面数据,还保存了 DOM 和 javascript 的状态;实际上是将整个页面都保存在了内存里。如果页面位于 bfcache 中,那么再次打开该页面时就不会触发 load 事件
注意事项
- 每次返回都会消耗一个 history 实体,若用户选择取消离开,则需要继续 pushSate 一个实体
- pushState 只能一个实体,多个实体返回会报错。使用 window.history.state 查询是否存在添加的实体。
例如,运行以下代码的http://example.com/example.html中的页面将按照指示生成警报:
1 | window.onpopstate = function (event) { |
请注意,即使原始历史记录项(for http://example.com/example.html)没有与其关联的状态对象,但当我们在第二次调用history.back()后激活该现时,仍然会触发一个popstate事件。
填坑史
这种东西一看就不靠谱,一用果然不靠谱,在移动端兼容好像还可以,目前没有发现在什么低端手机上完全不支持的。
需求是根据不同的情况来跳转,但是若是由接口返回参数进行判断的话则有可能用户在接口还没有反应的时候就进行了跳转,因此是使用的判断 cookies 的方式执行的 js。
业务场景:
页面 A->B->C,C 返回至 B 时页面返回时需要返回到指定的其他页面,比如跳到百度。
IOS 端 popstate 的怪异行为
由于 ios 的性能在缓存页面比较好,所以一般页面的后退都会保存之前的历史页面,不会触发页面上的 js 等,所以可以触发到 popstate 事件,而 webkit 的某些版本对 popstate 的理解与官方标准不一致,导致每次访问页面都会同步为访问了这个页面的历史纪录,所以 popstate 就被触发了。
问题:在 ios 上,页面 C 返回时,立即执行了 popstate 事件,导致直接跳转到百度了。
解决方法:通过 pageshow 事件处理
1 | var bool = false //定义一个变量 |
刷新时反复 pushState
因为刷新时重复 pushState,所以跳转到 baidu.com 时无法正常返回。
1 | if (!history.state || history.state.url !== '#') { |
感觉就是很不靠谱,所以使用的时候慎重加多测。