微信 H5 页面兼容性——卸载页面时可靠的发送 HTTP 请求

发布时间 2023-08-28 16:50:32作者: 艾前端

有时候,当用户离开网页时,需要向服务器提交一些用户行为的统计信息。这时,传统的请求方式,数据可能无法发送,因为浏览器已经把页面卸载了。

为了能够成功发出请求,并让服务器处理,需要解决两个问题:

  • 监听微信 H5 页面的卸载;
  • 在页面卸载事件里,在浏览器后台保持 HTTP 连接,继续发送数据。可靠的发送请求。

1. 监听 H5 页面的卸载

页面的卸载,细分为:

  • 锁屏
  • 切换页面
  • 切换到其它 app
  • 关闭当前页面

以上情况,都会导致手机将浏览器进程切换到后台,然后为了节省资源,可能就会杀死浏览器进程。为了解决这个问题,就诞生了 Page Visibility API。不管手机或桌面电脑,所有情况下,这个 API 都会监听到页面的可见性发生变化。

这个新的 API 的意义在于,通过监听网页的可见性,可以预判网页的卸载,还可以用来节省资源,减缓电能的消耗。比如,一旦用户不看网页,下面这些网页行为都是可以暂停的。

  • 对服务器的轮询
  • 网页动画
  • 正在播放的音频或视频

只要页面的可见性发生了变化,就会触发 visibilitychange 事件。因此,可以通过监听这个事件(通过 document.addEventListener() 方法或 document.onvisibilitychange 属性),跟踪页面可见性的变化。

document.addEventListener('visibilitychange', function () {
  // 用户离开了当前页面
  if (document.visibilityState === 'hidden') {
    document.title = '页面不可见';
  }

  // 用户打开或回到页面
  if (document.visibilityState === 'visible') {
    document.title = '页面可见';
  }
});

如果是在使用 vant 组件库,以 vue3 项目为例,可以通过监听 pageVisibility 事件,来处理 visible (可见)/ hidden (影藏)逻辑

const pageVisibility = usePageVisibility();
watch(pageVisibility, (value) => {
  console.log('VisibilityState: ', value); // type VisibilityState = 'visible' | 'hidden'; 页面当前的可见状态,visible 为可见,hidden 为隐藏
});

2. 微信浏览器中页面卸载兼容问题

手机锁屏、切换页面、切换到其它 app ,通过监听 visibilitychange 事件可以实现。但是点击浏览器左上角叉,或者 IOS 设备底部返回上一步却无法监听到。

这时,android 系统设备可以通过 unload 监听到点击浏览器左上角叉,IOS 系统设备可以通过 pagehide 监听点击设备底部返回上一步。

const isIOS = (): boolean => {
  const u = navigator.userAgent;
  return !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
};
// android 系统设备
function unloadHandler() {
  const isIOSDevice = isIOS();
  if (!isIOSDevice) {
    // 处理逻辑
  }
}

window.addEventListener('unload', unloadHandler, false);

window.removeEventListener('unload', unloadHandler, false);
// IOS 系统设备
function pagehideHandler() {
  const isIOSDevice = isIOS();
  if (isIOSDevice) {
    // 处理逻辑
  }
}

window.addEventListener('pagehide', pagehideHandler, false);

window.removeEventListener('pagehide', pagehideHandler, false);

3. 浏览器处理 HTTP 请求

当浏览器中的某个页面发生终止时,不能保证进程中的 HTTP 请求会成功。这些请求的可靠性可能取决于几件事——网络连接、应用程序性能,甚至外部服务本身的配置。

默认情况下,XHR 请求(通过 fetchXMLHttpRequest)是异步且非阻塞的。一旦请求排队,请求的实际工作就会被移交给幕后的浏览器级 API。

一旦页面开始被浏览器卸载并从内存中清除,页面就处于终止状态。在这种状态下没有新的任务可以启动,并且正在进行的任务如果运行时间过长可能会被杀死。

避免此问题的最明显方法可能是尽可能延迟用户操作,直到请求返回响应。通过使用 async/await 直到请求返回响应再真正卸载页面。

document.getElementById('link').addEventListener('click', async (e) => {
  e.preventDefault();

  // Wait for response to come back...
  await fetch("/log", {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    }, 
    body: JSON.stringify({
      some: 'data'
    }),
  });

  // ...and THEN navigate away.
   window.location = e.target.href;
});

这样做会反生延迟,让用户看起来页面卡死了,跳转其他页面没反应。

这个问题可以 使用 Fetch APIkeepalive 标志解决,告诉浏览器在后台保持连接,继续发送数据。

document.getElementById('link').addEventListener('click', (e) => {
  fetch("/log", {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    }, 
    body: JSON.stringify({
      some: 'data'
    }),
    keepalive: true
  });
});

4. Navigator.sendBeacon()

navigator.sendBeacon() 方法可用于通过 HTTP POST 将少量数据 异步 传输到 Web 服务器。它主要用于将统计数据发送到 Web 服务器,同时避免了用传统技术(如:XMLHttpRequest)发送分析数据的一些问题。

通常尝试在卸载(unload)文档之前向 Web 服务器发送数据。 异步地向服务器发送数据,同时不会延迟页面的卸载或影响下一导航的载入性能,这意味着:

  • 数据发送是可靠的。
  • 数据异步传输。
  • 不影响下一导航的载入。
  • 数据是通过 HTTP POST 请求发送的。
document.addEventListener("visibilitychange", function logData() {
  if (document.visibilityState === "hidden") {
    navigator.sendBeacon("/log", analyticsData);
  }
});

5. fetch:keepaliveNavigator.sendBeacon() 使用选择

适合用 ·fetch:keepalive· 方法:

  • 需要轻松地随请求传递自定义标头。
  • 想向 GET 服务发出请求,而不是 POST
  • 正在支持较旧的浏览器(如 IE)并且已经加载了 polyfill

sendBeacon() 在以下情况下可能是更好的选择:

  • 正在发出不需要太多自定义的简单服务请求。
  • 更喜欢更简洁、更优雅的 API。
  • 希望确保您的请求不会与应用程序中发送的其他高优先级请求竞争。

6. 参考链接