需求
我在做一个体验
当用户 submit enquiry 后会 window.open 开启 WhatsApp。而当用户关闭 WhatsApp 回来网站后,会 show 一个 feedback message。
实现思路
关键就在如何感知到,用户从 WhatsApp 切换回到了网站。
参考: Detect When Users Switch Tabs using JavaScript
监听 visibilitychange,然后通过 document.visibilityState 得知是 hidden or visible。
hidden 表示 leave tab,visible 表示 back to tab。
Android 鬼
本来是很简单的东西,但不知道是不是我手机的问题。Android 在开启 WhatsApp 的时候会触发 2 次。
Windows:open WhatsApp > hidden > close WhatsApp > visible
Android:open WhatsApp > hidden > visible > hidden > close WhatsApp > visible
显然 Windows 是正常的,Android 跑了多一次。
Workaround 就是尽可能判断出 Android 的情况,不要被它搞乱。我没有太认真去思路 right way,我觉得多半是一个 bug 来的,所以暂时解决就好了。
参考代码注释理解吧
const beforeWindowOpenTime = performance.now(); window.open(generateWhatsAppLink(whatsAppNumberBS.value, message), '_blank'); // note 解忧: // 这里有一个 user back from WhatsApp 后 show submitted feedback 的体验。 // 它比较复杂,因为手机有 bug, // 正常情况下 visibilitychange 会 dispatch when user leave/enter tab,离开 state = hidden,回来 state = visible。 // so when window open WhatsApp, dispatch lever tab 咯, when user close WhatsApp, dispatch enter tab 咯. // 但是我的 android 有鬼。 // when window open WhatsApp,它会先 leave + enter 1 次, 紧接着又 leave 1 次, 直到 user back // 所以下面代码需要顾虑它第一轮假的 leave + enter const [visible$, hidden$] = partition( fromEvent(window, 'visibilitychange').pipe(share()), () => document.visibilityState === 'visible', ); // 监听第一次 visible visible$.pipe(take(1)).subscribe(() => { const now = performance.now(); // 如果第一次 visible 超过 1 秒 if (now - beforeWindowOpenTime >= 1000) { // 直接 show feedback,因为 android 鬼是很快的,绝对不会超过 1 秒 showSubmittedFeedback(); return; } // 如果小于 1 秒,那可能是 android 鬼,或者 user 真的很快就 close WhatsApp // 我们尝试监听 hidden,如果是 android 鬼,那么 2 秒内会触发 hidden // 如果 2 秒后没有 hidden 那判定是 user close 很快。 hidden$.pipe(take(1), timeout({ first: 2000 })).subscribe({ next() { // android 鬼的话,等待下一次的 visible 表示 user close visible$.pipe(take(1)).subscribe(() => showSubmittedFeedback()); }, error() { // user close 的话,直接 show feedback showSubmittedFeedback(); }, }); function showSubmittedFeedback() { setTimeout(() => { submitFeedbackReCaptchaContainer.submitFeedbackReCaptchaContainerController.showSubmittedFeedback(); }, 1000); } });