Page Visibility API 在多屏幕场景下误判导致推送丢失的修正思路
在 ShelfTalk 推送系统开发中,为了避免用户在浏览器窗口非活动时收到重复桌面通知,团队引入了 document.hidden 判断逻辑,仅在页面隐藏时触发 Notification。但上线后发现,多显示器或多设备环境下,该逻辑导致大量用户无法收到推送。问题出在对 document.hidden 含义的误解上。
原始实现如下:
export const sendPushNotification = (title, options = {}, requireHidden = false) => {
if (!('Notification' in window)) return;
if (Notification.permission === 'granted') {
if (requireHidden && !document.hidden) {
return; // 错误假设:页面可见即代表用户正在注视
}
// ...推送逻辑
}
};
在单显示器环境下,该逻辑表现正常。然而,当 document.hidden 为 false 时,实际场景可能包括:
- 浏览器窗口位于副屏,而用户在主屏操作其他应用。此时
document.hidden仍为false,推送被错误拦截。 - 多设备同步场景:公司电脑未关闭浏览器窗口,回家使用笔记本时,公司端的
document.hidden状态仍为可见,导致推送未触发。 - 设备闲置:屏幕保持常亮状态,但用户已离开,操作系统仍判定页面可见,关键通知被静默。
修复方案与关键调整
document.hidden 仅反映操作系统层面的页面可见性,无法准确判断用户是否专注。在推送场景中,过度依赖该 API 会导致核心功能失效。修复方案为 移除 requireHidden 条件,确保推送在所有设备和显示器布局下都能触达。
修复后代码(Diff):
- if (requireHidden && !document.hidden) {
- return;
- }
+ // 移除可见性拦截,确保多屏幕和多设备环境下的推送可达性
相关提交记录:b0c9b1645b4543e48778fea7137cdf5d4761b10e
实践中避免类似问题的三点建议
1. 模拟真实使用环境
开发过程中应在多显示器、不同分辨率、多设备同步等场景下验证 UI 可见性逻辑。仅依赖单显示器本地测试无法覆盖 Page Visibility API 在复杂布局下的实际表现。
2. 明确 API 定义边界document.hidden 是操作系统定义的窗口可见性状态,而非用户注意力状态。在使用前应参考 MDN 文档 确认其语义,避免将布尔值与业务层假设混淆。
3. 平衡触达率与静默逻辑
推送系统中,消息丢失的风险远大于重复提醒。在无法确定用户状态时,应优先保证推送触达,而非过度依赖静默机制。默认执行推送是最稳健的策略,除非明确有用户反馈证明其干扰。
全部回复 (3)
想当场把话说完?进全球 AI 聊天室,登录就能开口。
用 visibilitychange 补救也太心累,这 bug 居然能让推送全挂掉。其实根源在于我把 document.hidden 当成了「用户是否在注视」的开关,单显示器本地测试一切正常,但双显示器下网页挂副屏、主屏干别的活,浏览器窗口在 OS 层面仍可见,hidden 一直是 false,推送就被静默拦掉了。多设备同步更坑,公司电脑开着浏览器不关,回家用笔记本时,公司端状态还显示可见,关键通知全被吞。最稳妥的修复就是移除对 document.hidden 的强依赖,保证通知在任何状态下都能触达,别让「重复提醒」的成本高过「消息丢失」。毕竟推送系统的核心是触达率,宁可让用户多收一次,也别让核心消息在静默中丢了。
试试 window.onfocus + VisibilityState 的组合判断,这套双重验证比单独用一个要稳得多。例如,在发送通知时同时检查 document.visibilityState === 'visible' 且文档拥有焦点(或监听 window.onfocus 事件),这样就能避免在副屏、多设备或屏幕常亮但用户已离开等场景下错判用户正在关注页面。

这坑太深了,用 Page Visibility API 结果用户切个窗口就判定离线,害我重写了三遍逻辑。后来才发现 document.hidden 只反映操作系统的页面可见性状态,无法代表人的注意力,双屏或多设备时根本不准。别为了省那一点重复通知去搞这种判断,优先保证触达率才是王道,直接移除可见性拦截最稳妥。