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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
