需求
我在做一個體驗
當用戶 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); } });