
本文介绍如何利用页面可见性 API 捕获用户关闭标签页、切换窗口或导航离开前的最后一次输入变更,确保未触发 change 事件的数据仍能及时保存。
本文介绍如何利用页面可见性 api 捕获用户关闭标签页、切换窗口或导航离开前的最后一次输入变更,确保未触发 `change` 事件的数据仍能及时保存。
在 Web 表单场景中(例如工时填报页),依赖 input 或 textarea 的 change 事件(如 jQuery 的 .change())常存在一个隐蔽缺陷:该事件仅在元素失去焦点(blur)且值发生改变后触发。当用户在最后一个输入框完成编辑后直接关闭浏览器标签页、点击链接跳转或按 Alt+Tab 切换窗口时,焦点未正常转移,change 事件永不触发,导致最新数据丢失。
为解决此问题,推荐采用 visibilitychange 事件 + navigator.sendBeacon() 的组合方案——它不依赖焦点行为,而是监听页面整体可见性状态变化,精准覆盖“即将卸载”的关键时机。
✅ 推荐实现方式
// 监听页面可见性变化
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
// 页面即将不可见(如关闭、最小化、切到其他标签页)
const unsavedData = collectFormData(); // 自定义函数:序列化所有待保存字段
if (Object.keys(unsavedData).length > 0) {
// 使用 sendBeacon 发送数据(可靠、异步、不阻塞卸载)
navigator.sendBeacon('/api/save-hours', new URLSearchParams(unsavedData));
}
}
});
// 示例:收集表单数据(可根据实际结构调整)
function collectFormData() {
const data = {};
document.querySelectorAll('input[type="text"][name^="hours-"]').forEach(el => {
if (el.value.trim() !== '') {
data[el.name] = el.value;
}
});
return data;
}
⚠️ 关键注意事项
-
sendBeacon()仅支持 POST 请求,且不返回响应(无法确认服务端是否成功处理)。但它被设计为“尽力而为”:即使页面已开始卸载,浏览器仍会尝试将数据发送至服务器,成功率远高于普通fetch()或XMLHttpRequest。 - 若需更精细控制(如错误重试、JSON 体),可改用
keepalive: true的fetch()(Chrome/Edge 支持,Firefox 当前暂不支持):fetch('/api/save-hours', { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' }, keepalive: true // 允许在页面卸载后继续发送 }); -
visibilitychange触发时机早于beforeunload,且无用户交互限制(beforeunload已被现代浏览器限制弹窗并逐步弃用),因此更可靠、更符合用户体验预期。 - 建议配合防抖或本地存储(如
localStorage)做双重保障:每次输入后短暂缓存,再通过visibilitychange统一提交,避免重复提交或网络失败导致的数据丢失。
通过该方案,你无需引入 keyup 等高频事件影响性能,也无需强制用户点击提交按钮,即可在“零操作干预”前提下,稳健捕获最后时刻的关键变更。









