CSS & JS Effect – Do something on enter/leave window tab

发布时间 2023-10-05 01:52:28作者: 兴杰

需求

我在做一个体验

当用户 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);
  }
});