Intercepting Browser Navigation Events (Back/Forward, Refresh/Close, and Route Changes) in Vue Applications
This article explains how to prevent loss of unsaved video data by intercepting browser back/forward actions, page refresh or close events, and Vue router navigation using the History API, beforeunload listener, and navigation guards, with complete Vue code examples.
The author shares a practical problem: a page that records video stores the data in memory until the user saves it, so navigating away (back/forward, refresh, close, or route change) would discard the video, requiring interception of these actions.
Requirements : intercept (1) browser back/forward, (2) tab refresh and close, (3) Vue router navigation.
1. Intercept Browser Back/Forward : Since browsers block direct JS interception, a "trick" using history.pushState creates a dummy history entry so that a popstate event fires on back navigation. The handler shows a modal, and if the user confirms, history.go(-2) skips the dummy entries.
import { onUnmounted } from 'vue'
interface IBrowserInterceptEvents {
popstate?: (next: () => void) => void // 监听浏览器前进后退
}
// 作用:添加一个历史记录,以便后续模拟拦截后退
function addStopHistory() {
const state = { id: 'stopBack' }
if (history.state.id === 'stopBack') return
history.pushState(state, '', window.location.href)
}
const useBrowserInterceptor = (events: IBrowserInterceptEvents) => {
const { popstate } = events
let popstateCallback: EventListener | undefined
let isHistoryBack = false
// 拦截浏览器后退
if (popstate) {
addStopHistory()
popstateCallback = () => {
addStopHistory()
popstate(() => {
isHistoryBack = true
history.go(-2)
})
}
window.addEventListener('popstate', popstateCallback)
}
// 销毁事件
onUnmounted(() => {
// 不是历史后退触发的,仅仅是组件卸载,才需要清除模拟拦截后退时添加的历史记录
if (popstate && !isHistoryBack) {
history.go(-1)
}
popstateCallback && window.removeEventListener('popstate', popstateCallback)
})
}
export default useBrowserInterceptorUsage :
// 使用拦截
useBrowserInterceptor({
popstate: showWarnModal,
})
// 弹窗提示
const showWarnModal = (next: any) => {
const { pending, uploading, failed } = taskStatusMap.value
if (pending + uploading + failed > 0) {
Modal.confirm({
title: h('h3', '当前页面有未完成的任务!'),
width: 500,
content: h('div', null, [
taskStatusMap.value.pending
? h(Tag, { color: 'default' }, `待上传:${taskStatusMap.value.pending}`)
: null,
taskStatusMap.value.uploading
? h(Tag, { color: 'processing' }, `上传中:${taskStatusMap.value.uploading}`)
: null,
taskStatusMap.value.failed
? h(Tag, { color: 'error' }, `上传失败:${taskStatusMap.value.failed}`)
: null,
h(
'div',
{ style: { marginTop: '10px' } },
'此操作会导致未完成上传的视频数据丢失,确定要继续吗?'
)
]),
onOk() {
next()
}
})
} else {
next()
}
}2. Intercept Tab Refresh and Close : Listen to the beforeunload event and prevent the default action. Browsers only allow the default confirmation dialog, not custom text.
import { onUnmounted } from 'vue'
interface IBrowserInterceptEvents {
popstate?: (next: () => void) => void // 监听浏览器前进后退
beforeunload?: EventListener // 监听标签页刷新和关闭
}
// addStopHistory ...
const useBrowserInterceptor = (events: IBrowserInterceptEvents) => {
const { popstate, beforeunload } = events
let popstateCallback: EventListener | undefined
let beforeunloadCallback: EventListener | undefined
let isHistoryBack = false
// 拦截浏览器后退 ...
// 拦截标签页关闭和刷新
if (beforeunload) {
beforeunloadCallback = (event) => {
if (!isHistoryBack) beforeunload(event)
}
window.addEventListener('beforeunload', beforeunloadCallback)
}
// 销毁事件
onUnmounted(() => {
// 不是后退且不是导航守卫触发的,仅仅是组件卸载,才需要清除模拟拦截后退时添加的历史记录
if (popstate && !isHistoryBack) {
history.go(-1)
}
popstateCallback && window.removeEventListener('popstate', popstateCallback)
beforeunloadCallback && window.removeEventListener('beforeunload', beforeunloadCallback)
})
}
export default useBrowserInterceptorUsage example adds both popstate and beforeunload handlers to show a warning when there are pending uploads.
useBrowserInterceptor({
popstate: showWarnModal,
beforeunload: (e) => {
const { pending, uploading, failed } = taskStatusMap.value
if (pending + uploading + failed > 0) {
e.preventDefault()
e.returnValue = false
}
}
})3. Intercept Route Navigation (Full Version) : Use Vue Router's onBeforeRouteLeave guard together with the previous mechanisms. An isRouter flag distinguishes route navigation from history back.
import { onUnmounted } from 'vue'
import { type NavigationGuardNext, onBeforeRouteLeave } from 'vue-router'
interface IBrowserInterceptEvents {
popstate?: (next: () => void) => void // 监听浏览器前进后退
beforeunload?: EventListener // 监听标签页刷新和关闭
beforeRouteLeave?: (next: NavigationGuardNext) => void // 导航守卫
}
// 作用:添加一个历史记录,以便后续模拟拦截后退
function addStopHistory() {
const state = { id: 'stopBack' }
if (history.state.id === 'stopBack') return
history.pushState(state, '', window.location.href)
}
const useBrowserInterceptor = (events: IBrowserInterceptEvents) => {
const { popstate, beforeunload, beforeRouteLeave } = events
let popstateCallback: EventListener | undefined
let beforeunloadCallback: EventListener | undefined
let isHistoryBack = false
let isRouter = false
// 拦截浏览器后退
if (popstate) {
addStopHistory()
popstateCallback = () => {
addStopHistory()
popstate(() => {
isHistoryBack = true
history.go(-2)
})
}
window.addEventListener('popstate', popstateCallback)
}
// 拦截标签页关闭和刷新
if (beforeunload) {
beforeunloadCallback = (event) => {
if (!isHistoryBack) beforeunload(event)
}
window.addEventListener('beforeunload', beforeunloadCallback)
}
// 导航守卫
beforeRouteLeave &&
onBeforeRouteLeave((_to, _from, next) => {
if (isHistoryBack) {
next()
return
}
beforeRouteLeave(() => {
isRouter = true
next()
})
})
// 销毁事件
onUnmounted(() => {
// 不是后退且不是导航守卫触发的,仅仅是组件卸载,才需要清除模拟拦截后退时添加的历史记录
if (popstate && !isHistoryBack && !isRouter) {
history.go(-1)
}
popstateCallback && window.removeEventListener('popstate', popstateCallback)
beforeunloadCallback && window.removeEventListener('beforeunload', beforeunloadCallback)
})
}
export default useBrowserInterceptorUsage combines all three handlers to protect unsaved video uploads across navigation types.
// 使用拦截
useBrowserInterceptor({
beforeRouteLeave: showWarnModal,
popstate: showWarnModal,
beforeunload: (e) => {
const { pending, uploading, failed } = taskStatusMap.value
if (pending + uploading + failed > 0) {
e.preventDefault()
e.returnValue = false
}
}
})
// 弹窗提示同前文Conclusion : By intercepting user actions such as refresh, tab close, browser back/forward, and route changes, developers can present friendly warnings and prevent accidental loss of in‑progress video uploads, thereby improving user experience.
References : Articles on History API, beforeunload handling, and Vue router navigation guards.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.