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

🧑‍💻 写在开头
点赞 + 收藏 === 学会🤣🤣🤣

需求

首先列举一下需要拦截的行为,接下来我们逐个实现。

  1. 浏览器前进后退
  2. 标签页刷新和关闭
  3. 路由跳转

1、拦截浏览器前进后退

这里的实现是核心,涉及到大量 History API 的理解,如果不太了解可以先看一下这两个文章:
拦截浏览器后退方法附带独家干货知识点
浏览器的History、Location对象,及使用js控制网页的前进后退和加载,刷新当前页面总结!

首先给大家明确一点,出于安全问题,浏览器并不支持通过js拦截浏览器的前进后退操作,但是可以使用障眼法。
具体思路就是我们可以在页面加载的时候,使用 history.pushState 这个API给页面添加一个当前页面的历史记录(不会导致页面刷新),此时最近的两条历史记录都是当前页面,当用户点击后退的时候,浏览器会退到上一个记录(还是当前页面),这时会触发 popstate事件 ,回退的时候再往历史记录里添加一条当前页面的记录(为了下次拦截使用),同时我们使用弹窗提示用户一些信息,如果用户确定要回退,我们再使用 history.go(-2) 跳过这两条当前页面的记录,返回到真正的上个页面,这样我们就成功模拟了回退操作的拦截。
在这里插入图片描述

代码实现:

import { onUnmounted } from 'vue'interface IBrowserInterceptEvents {popstate?: (next: () => void) => void // 监听浏览器前进后退
}// 作用:添加一个历史记录,以便后续模拟拦截后退
function addStopHistory() {const state = { id: 'stopBack' }if (history.state.id === 'stopBack') returnhistory.pushState(state, '', window.location.href)
}const useBrowserInterceptor = (events: IBrowserInterceptEvents) => {const { popstate } = eventslet popstateCallback: EventListener | undefinedlet isHistoryBack = false// 拦截浏览器后退if (popstate) {addStopHistory()popstateCallback = () => {addStopHistory()popstate(() => {isHistoryBack = truehistory.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.valueif (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 事件,阻止默认行为即可。但是这里要注意:出于浏览器安全问题,我们只能使用浏览器默认弹窗提示(如下图),无法自定义提示内容。

历史回退也有可能导致触发 beforeunload 事件,所以要添加一个 isHistoryBack 变量做判断区分。

刷新页面:

在这里插入图片描述

关闭页面:

在这里插入图片描述

代码实现

import { onUnmounted } from 'vue'interface IBrowserInterceptEvents {popstate?: (next: () => void) => void // 监听浏览器前进后退beforeunload?: EventListener // 监听标签页刷新和关闭
}// addStopHistory ...const useBrowserInterceptor = (events: IBrowserInterceptEvents) => {const { popstate, beforeunload } = eventslet popstateCallback: EventListener | undefinedlet beforeunloadCallback: EventListener | undefinedlet 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.valueif (pending + uploading + failed > 0) {e.preventDefault()e.returnValue = false}}
})

3、拦截路由跳转(完整版)

这里我们可以使用 vue-router 提供的 onBeforeRouteLeave 钩子函数在组件内注册一个导航守卫,当用户跳转路由的时候进行弹窗提示。

历史回退也有可能触发导航守卫,也要使用 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') returnhistory.pushState(state, '', window.location.href)
}const useBrowserInterceptor = (events: IBrowserInterceptEvents) => {const { popstate, beforeunload, beforeRouteLeave } = eventslet popstateCallback: EventListener | undefinedlet beforeunloadCallback: EventListener | undefinedlet isHistoryBack = falselet isRouter = false// 拦截浏览器后退if (popstate) {addStopHistory()popstateCallback = () => {addStopHistory()popstate(() => {isHistoryBack = truehistory.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 = truenext()})})// 销毁事件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.valueif (pending + uploading + failed > 0) {e.preventDefault()e.returnValue = false}}
})// 弹窗提示
const showWarnModal = (next: any) => {const { pending, uploading, failed } = taskStatusMap.valueif (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()}
}

总结

我们实现了对 用户刷新、关闭标签页、浏览器历史回退、路由跳转 等操作的拦截,可以在某些特殊场景下给用户一些友好的提示,提升用户体验。

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/diannao/84333.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker:容器化技术

引言 传统部署环境逐渐不适应现在的企业开发,为了追求更加轻量,更加容易管理项目,引入了docker容器化技术去实现更加高效的部署环境。 一.docker风光下的内核功能和常用命令 1.docker容器和虚拟机的区别 我们在底层和应用层之间引入了一层do…

ping命令常用参数以及traceout命令

在网络故障排查和性能分析中,ping和 traceroute(Windows中通常称为 tracert)是两个极为重要的工具。它们帮助诊断网络连接问题,了解数据在网络中的传输路径。下面将详细介绍这两个命令的常用参数及其应用。 ping命令 ping命令用…

SpringBoot开发——Spring Boot异常处理全攻略:五大方案实战对比

文章目录 一、血泪教训:异常处理的代价二、五大异常处理方案详解2.1 全局异常处理(推荐方案)2.2 控制器级处理2.3 HTTP状态码注解2.4 ResponseEntity精细控制2.5 自定义异常体系(企业级方案)三、五大方案对比决策表四、四大避坑指南4.1 异常吞噬陷阱4.2 循环依赖问题4.3 异…

CodeBuddy 实现图片转素描手绘工具

本文所使用的 CodeBuddy 免费下载链接:腾讯云代码助手 CodeBuddy - AI 时代的智能编程伙伴 前言 最近在社交媒体上,各种素描风格的图片火得一塌糊涂,身边不少朋友都在分享自己的 “素描照”,看着那些黑白线条勾勒出的独特韵味&a…

2025.05.21华为暑期实习机考真题解析第二题

📌 点击直达笔试专栏 👉《大厂笔试突围》 💻 春秋招笔试突围在线OJ 👉 笔试突围OJ 02. 灾区物资调度路径规划 问题描述 在一次严重的自然灾害后,LYA负责协调救援物资的配送工作。救援区域包含多个受灾乡镇和一个物资集结点,各个地点之间的道路状况各异,有些甚至…

Gartner《Optimize GenAI Strategy for 4 Key ConsumerMindsets》学习心得

一、引言 在当今数字化营销浪潮中,生成式人工智能(GenAI)正以前所未有的速度重塑着市场格局。GenAI 既是一场充满机遇的变革,也是一场潜在风险的挑战。一方面,绝大多数 B2C 营销领导者对 GenAI 赋能营销抱有极高期待,他们看到了 GenAI 在提升时间与成本效率方面的巨大潜…

探索链表的奇妙世界:从基础到高级应用

链表是计算机科学中一种基础且重要的数据结构,它如同一条由珠子串成的项链,每个珠子(节点)都包含着数据和指向下一个珠子的线索。 与数组相比,链表在插入和删除操作上更加灵活,无需预先分配固定大小的内存…

黑马点评双拦截器和Threadlocal实现原理

文章目录 双拦截器ThreadLocal实现原理 双拦截器 实现登录状态刷新的原因: ​ 防止用户会话过期:通过动态刷新Token有效期,确保活跃用户不会因固定过期时间而被强制登出 ​ 提升用户体验:用户无需频繁重新登录,只要…

Windows 中动态库.dll 的 .lib 文件有什么作用?

在 Windows 平台开发中, 动态链接库(Dynamic Link Library, DLL)。与之相关的还有一个常让人困惑的文件——.lib 文件。那么,这个 .lib 文件到底有什么作用呢? 一、什么是 .lib 文件? .lib 文件是 静态导入库(Import Library) 文件,它通常与动态链接库(DLL)一起生成…

细说STM32单片机FreeRTOS消息缓冲区及其应用实例

目录 一、消息缓冲区功能概述 二、消息缓冲区操作相关函数 1、相关函数概述 2、部分函数详解 (1)创建消息缓冲区 (2)写入消息 (3)读取消息 (4)消息缓冲区状态查询 三、消息…

【缓存】JAVA本地缓存推荐Caffeine和Guava

🌟 引言 在软件开发过程中,缓存是提升系统性能的常用手段。对于基础场景,直接使用 Java集合框架(如Map/Set/List)即可满足需求。然而,当面对更复杂的缓存场景时: 需要支持多种过期策略&#x…

IDA插件 MIPSROP的安装和使用方法

前言 笔者的IDA版本为9.0,刚开始根据一些博客描述以为将mipsrop.py拷贝到IDA的plugins目录即可,可操作后发现事情好像没这么简单,复制进去后就发现没有博客中所说的 MIPS ROP Finder ,笔者在网上搜索了很多博客后在 https://bbs.…

(1)转置后,行列式的值不变 (2)将行列式的任意两行互换位置后,行列式改变符号

以下是对原始内容在不改变内容本身的前提下进行的格式优化,以提升可读性和逻辑清晰度: ✅ 行列式的几何意义 行列式(determinant)是线性代数中一个非常重要的概念,它的几何含义可以从以下几个方面理解: &a…

最大似然估计(Maximum Likelihood Estimation, MLE)详解

一、定义 最大似然估计 是一种参数估计方法,其核心思想是: 选择能使观测数据出现概率最大的参数值作为估计值。 具体来说,假设数据 D x 1 , x 2 , … , x n D{x_1,x_2,…,x_n} Dx1​,x2​,…,xn​独立且服从某个概率分布 P ( x ∣ θ ) P(…

用go从零构建写一个RPC(3)--异步调用+多路复用实现

在前两个版本中,我们实现了基础的客户端-服务端通信、连接池、序列化等关键模块。为了进一步提升吞吐量和并发性能,本版本新增了 异步发送机制 和 多路复用支持,旨在减少资源消耗、提升连接利用率。 代码地址:https://github.com/…

FFmpeg 安装包全攻略:gpl、lgpl、shared、master 区别详解

这些 FFmpeg 安装包有很多版本和变种,主要区别在于以下几个方面: ✅ 一、从名称中看出的关键参数: 1. 版本号 master:开发版,最新功能,但可能不稳定。n6.1 / n7.1:正式版本,更稳定…

深度学习实战:从图像分类到文本生成的完整案例解析

1 图像分类案例 1.1 CIFAR10数据集介绍 cifar数据是torchvision第三方包提供的数据集 训练集5w 测试集1w y标签 10个类别 10分类问题 一张图形状 (32, 32, 3) import torch import torch.nn as nn from torchvision.datasets import CIFAR10 from torchvision.transforms i…

Android 添加系统服务的完整流程

[应用程序] (应用进程)│↓ 调用简单API [SoundManager] │ ├─ 代理模式门面模式(应用进程)│ ├─ 缓存数据 ←─ 装饰器模式(应用进程)│ └─ 转换异常 ←─ 适配器模式(应用进程)│↓ 通过Bind…

wan2.1代码笔记

GPU内存不够,可以先运行umt5,然后再运行wanpipeline,参考FLUX.1代码笔记,或者使用ComfyUI。 下面使用随机数代替umt5 embedding。 import torch from diffusers.utils import export_to_video from diffusers import Autoencoder…

环境搭建与工具配置

3.1 本地环境搭建 3.1.1 WAMP环境搭建漏洞靶场(一、二) WAMP(Windows Apache MySQL PHP)是搭建本地Web漏洞靶场的基础环境。 安装步骤: Apache:下载并安装最新版Apache HTTP Server,配置监…