Skip to content

useHistoryBackTrap 阻止浏览器返回

阻止浏览器返回操作,可在用户编辑页面未保存时做出提醒,不依赖 Vue 原生 js 中也可以使用

代码实现

ts
export function useHistoryBackTrap(cb: () => boolean | void | Promise<boolean | void>) {
  const name = 'use-history-back-trap';
  const isTraping = () => window.history.state === name;

  async function popstate(_event: PopStateEvent) {
    if (isTraping()) return;
    window.history.forward();
    while (true) {
      await new Promise(resolve => requestAnimationFrame(resolve));
      if (isTraping()) break;
    }

    if (await cb()) {
      await destroy();
      window.history.back();
    }
  }

  function start() {
    if (!isTraping()) window.history.pushState(name, name, window.location.href);
    window.removeEventListener('popstate', popstate);
    window.addEventListener('popstate', popstate);
  }

  async function destroy() {
    window.removeEventListener('popstate', popstate);
    if (!isTraping()) return;
    window.history.back();
    while (true) {
      await new Promise(resolve => requestAnimationFrame(resolve));
      if (!isTraping()) break;
    }
  }

  return { start, destroy };
}

使用示例

vue
<script lang="ts" setup>
import { Dialog } from 'vant';
import { ref } from 'vue-demi';
import { useHistoryBackTrap } from './use-history-back-trap';

const isModify = ref<boolean>(false)

const historyBackTrap = useHistoryBackTrap(() => {
  // 若未修改内容,则不提示
  if (!isModify.value) return true;
  // 返回 true 取消拦截,返回 false 拦截本次返回
  return Dialog.confirm({
    message: '内容还没有提交,返回后您编辑的内容将失效',
    cancelButtonText: '我再想想',
    confirmButtonText: '确认返回'
  })
    .then(() => true)
    .catch(() => false);
});
// 在页面无交互之前不要调用 `start()` 部分浏览器会阻止此操作
historyBackTrap.start();

// 跳转其他页面或不需要拦截时需调用 historyBackTrap.destroy()
function handleGoOtherPage() {
  await historyBackTrap.destroy();
  router.push('/other-page');
}
</script>