全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2025-03-17_优化用户体验:拦截浏览器前进后退、刷新、关闭、路由跳转等用户行为并弹窗提示

您的位置:首页 >> 新闻 >> 行业资讯

优化用户体验:拦截浏览器前进后退、刷新、关闭、路由跳转等用户行为并弹窗提示 点击关注公众号,“技术干货”及时达! 前言前几天在工位正在摸鱼的时候,技术组长悄咪咪走到了我的身后 站了一会之后拍拍我的肩膀说:"大佬这有一个功能需要你优化一下"。 简单给 JYM 叙述一下这个需求:就是我们有一个页面可以调用设备的摄像头拍摄视频,拍完之后用户可以把拍摄的视频直接上传或者保存到本地。但是用户如果在上传之前没有保存到本地,那么这段视频是存储到内存里的,当用户手动点击页面刷新、关闭、历史回退、路由跳转等操作,都会导致这段视频资源丢失,此前我们给了用户提示,但是以防万一,还是要拦截一下用户的这些操作并告知其风险才行。 需求并不复杂,但是不太常见,我在迭代的时候遇到了一些坑,分享出来,大家如果也有这样的需求可以参考一下。 需求首先列举一下需要拦截的行为,接下来我们逐个实现。 浏览器前进后退标签页刷新和关闭路由跳转1、拦截浏览器前进后退?这里的实现是核心,涉及到大量 History API[1] 的理解,如果不太了解可以先看一下这两个文章: 拦截浏览器后退方法附带独家干货知识点[2] 浏览器的 History、Location 对象,及使用 js 控制网页的前进后退和加载,刷新当前页面总结![3] ?首先给大家明确一点,出于安全问题,浏览器并不支持通过 js 拦截浏览器的前进后退操作,但是可以使用「障眼法」。 具体思路就是我们可以在页面加载的时候,使用 history.pushState[4] 这个 API 给页面添加一个当前页面的历史记录(不会导致页面刷新),此时最近的两条历史记录都是当前页面,当用户点击后退的时候,浏览器会退到上一个记录(还是当前页面),这时会触发 popstate 事件[5] ,回退的时候再往历史记录里添加一条当前页面的记录(为了下次拦截使用),同时我们使用弹窗提示用户一些信息,如果用户确定要回退,我们再使用 「history.go(-2)」 跳过这两条当前页面的记录,返回到真正的上个页面,这样我们就成功模拟了回退操作的拦截。 代码实现: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 useBrowserInterceptor 使用// 使用拦截 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、拦截标签页刷新和关闭这个比较简单,我们只需要监听 beforeunload[6] 事件,阻止默认行为即可。但是这里要注意:出于浏览器安全问题,我们只能使用浏览器默认弹窗提示(如下图),无法自定义提示内容。 ?历史回退也有可能导致触发 「beforeunload」 事件,所以要添加一个 「isHistoryBack」 变量做判断区分。 ?「刷新页面:」 「关闭页面:」 代码实现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 useBrowserInterceptor 使用useBrowserInterceptor({ popstate: showWarnModal, beforeunload: (e) = { const { pending, uploading, failed } = taskStatusMap.value if (pending + uploading + failed 0) { e.preventDefault() e.returnValue = false } } }) 3、拦截路由跳转(完整版)这里我们可以使用 「vue-router」 提供的 onBeforeRouteLeave[7] 钩子函数在组件内注册一个导航守卫,当用户跳转路由的时候进行弹窗提示。 ?历史回退也有可能触发导航守卫,也要使用 「isHistoryBack」 做判断区分。 「最后我们还要处理一下事件的销毁,组件卸载时销毁事件,这里有个注意点:我们不仅要移除注册的事件,当组件卸载不是历史后退(isHistoryBack)也不是路由跳转(isRouter)触发的,仅仅是组件卸载(比如 v-if),这个时候还需要清除模拟拦截后退时添加的历史记录,否则会造成页面回退异常。」 ?代码实现(完整版)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 useBrowserInterceptor 使用// 使用拦截 useBrowserInterceptor({ beforeRouteLeave: showWarnModal, popstate: showWarnModal, beforeunload: (e) = { const { pending, uploading, failed } = taskStatusMap.value if (pending + uploading + failed 0) { e.preventDefault() e.returnValue = false } } }) // 弹窗提示 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() } } 总结我们实现了对 「用户刷新、关闭标签页、浏览器历史回退、路由跳转」 等操作的拦截,可以在某些特殊场景下给用户一些友好的提示,提升用户体验。 参考文章拦截浏览器后退方法附带独家干货知识点[8] 浏览器的 History、Location 对象,及使用 js 控制网页的前进后退和加载,刷新当前页面总结![9] 点击关注公众号,“技术干货”及时达! 阅读原文

上一篇:2019-12-30_2020年春节档前瞻:重磅国产影片即将展开竞争 |凡影数读 下一篇:2024-09-20_js中的预编译,你真的懂了吗?

TAG标签:

12
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价