点击回退页面时阻止并进行自定义操作


操作与监听浏览器历史记录

浏览器提供了几个 api 用于操作/监听浏览器历史记录

onpopstate

对于该需求,首先不难想到的是我们可以找到一种办法来监听页面回退事件。浏览器为我们提供了 onpopstate 事件来监听同一文档中不同的历史记录条目的变更。

只有用户点击浏览器的前进或后退按钮时,或是 JavaScript 执行 history.back()history.forward()history.go() 方法时,才会触发该事件。

相关详细内容可以参考文档 WindowEventHandlers.onpopstate

1
2
3
window.addEventListener("popstate", e => {
console.log( e.state === history.state ); // true
});

需要注意的是,onpopstate 事件在页面初次加载时并不会被触发。

不过遗憾的是,该事件仅能被监听而不能被阻止。我们需要 pushStatereplaceState 来辅助我们实现需求。

history.pushState()

浏览器在 history 上为我们提供了一个方法 pushState(),该方法向当前浏览器会话的历史堆栈中添加一个状态(state)。

1
history.pushState( state, title, url );

该方法接受三个参数:

  • state:与本次添加的新历史条目相关联的 state 状态,可以通过 history.state 获取。
  • title:新页面的标题,目前大多数浏览器不支持,传入 null 或空字符串即可。
  • url:新页面的 URL,必须与当前页面同源,否则会抛出异常。不设置时默认为当前文档的 URL

此状态变更不会触发页面刷新,仅是导致 History 对象发生变化,地址栏会有反应。不会触发 onpopstate 事件,诸如 vue 的生命周期也不会被触发。
同理,设置的 URL 参数为一个新的锚点值时,也不会触发 hashchange 事件。

1
2
history.pushState( { temp: true }, null );
console.log( history.state.temp ); // true

history.replaceState()

replaceState() 方法与 pushState() 方法类似,但是它是用新的 state 替换当前的历史记录条目,而不是添加一个新的条目。

1
2
history.replaceState( { temp: false }, null );
console.log( history.state.temp ); // false

实现

通过这两个 api,我们可以确定实现思路。

  • 使用 pushState 添加一个当前页面的重复 state,使得浏览器执行回退操作时仅回退掉我们新增的这个 state。
  • 使用 onpopstate 监听页面状态变化,执行我们需要的操作。

下面实现一个点击回退关闭弹窗的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const popstateListener = e => {
// 关闭弹窗
closeDialog()
}

onMounted(() => {
// 弹窗打开时,手动添加占位历史记录
history.pushState({ dialogTemp: true }, null);
addEventListener("popstate", popstateListener, false);
})

onBeforeUnmount(() => {
// 此时未执行浏览器返回事件,清除手动添加的占位历史记录,应对手动点击关闭弹窗按钮的情况
if ( history.state.dialogTemp ) {
history.back();
}
window.removeEventListener("popstate", popstateListener);
})